小编典典

在uiwebview中使用javascript将样式应用于文本范围

css

我在iPhone上的UIWebView中将一些简单样式的文本显示为html。它基本上是一系列段落,偶尔带有强或强调的短语。在运行时,我需要将样式应用于文本范围。

有几种类似的情况,其中一种是突出显示搜索结果。如果用户搜索了“某物”,我想更改单词出现后的背景颜色,然后再恢复原始背景。

是否可以使用javascript将样式应用于文本范围?其中一个关键部分是能够取消设置样式。

似乎有两种可能的方法。一种方法是在Objective-
C中修改一些html,并通过javascript将其作为某些容器的新innerHTML传递。另一种方法是使用javascript直接操作DOM节点。

我可以操纵html,但这在Objective-
C中听起来很乏味,因此,如果那是一种合理的方法,我宁愿操纵DOM。我对javascript和DOM不太熟悉,所以我不知道这是否是一种合理的方法。

我编写了一些例程来在文本范围和带有偏移量的节点范围之间进行转换。因此,如果我从100-200的文本范围开始,并在一个段落中开始,在第三段中结束,则可以获得文本节点以及代表给定文本范围的节点中的偏移量。我只需要一种在文本偏移处拆分文本节点的方法。目前,我只是将样式应用于包含文本范围的段落。

一些注意事项:

  • 请直接使用javascript,没有像jquery这样的外部框架。
  • 更改无需再写入磁盘。
  • 更改应该是不可撤销的,或者至少是可删除的。
  • css文件中已经存在要应用的样式。
  • 它需要在iPhone 3.0及更高版本中工作。
  • 应用程序附带了所有源文件。
  • 请详细。

感谢您的任何建议。


阅读 282

收藏
2020-05-16

共1个答案

小编典典

我认为您要为此寻求一个完整的解决方案的要求很高,但是这似乎很有趣,因此我已经实施了它。以下内容可在最新的WebKit浏览器中使用,包括运行OS
3.0的iPhone上的Safari。它使用WebKit中存在的非标准但方便的intersectsNode方法Range,但在3.0中已从Firefox中删除了它,因此它在Firefox的最新版本中不起作用,但可以做到这一点。

下面将用一个<span>具有“
someclass”类和一个唯一类的元素围绕每个选定的文本节点,以方便撤消。applyClassToSelection返回这个唯一的类;将此类传递removeSpansWithClass给以删除跨度。

更新: 解决了当选择完全包含在单个文本节点中时的问题

更新2: 现在经过测试,可以在运行OS 3.0的iPhone中使用。

更新3: 添加了rangeIntersectsNode功能,以添加对Firefox 3.0及更高版本的支持。现在,该代码应可在Firefox
1.0 +,Safari 3.1 +,Google Chrome,Opera 9.6+以及其他版本(到目前为止尚未试用)中使用。
它根本无法在Internet Explorer中运行,并且会在该浏览器中显示错误。 我计划尽快开发IE版本。

<script type="text/javascript">
    var nextId = 0;

    var rangeIntersectsNode = (typeof window.Range != "undefined"
            && Range.prototype.intersectsNode) ?

        function(range, node) {
            return range.intersectsNode(node);
        } :

        function(range, node) {
            var nodeRange = node.ownerDocument.createRange();
            try {
                nodeRange.selectNode(node);
            } catch (e) {
                nodeRange.selectNodeContents(node);
            }

            return range.compareBoundaryPoints(Range.END_TO_START, nodeRange) == -1 &&
                range.compareBoundaryPoints(Range.START_TO_END, nodeRange) == 1;
        };

    function applyClassToSelection(cssClass) {
        var uniqueCssClass = "selection_" + (++nextId);
        var sel = window.getSelection();
        if (sel.rangeCount < 1) {
            return;
        }
        var range = sel.getRangeAt(0);
        var startNode = range.startContainer, endNode = range.endContainer;

        // Split the start and end container text nodes, if necessary
        if (endNode.nodeType == 3) {
            endNode.splitText(range.endOffset);
            range.setEnd(endNode, endNode.length);
        }

        if (startNode.nodeType == 3) {
            startNode = startNode.splitText(range.startOffset);
            range.setStart(startNode, 0);
        }

        // Create an array of all the text nodes in the selection
        // using a TreeWalker
        var containerElement = range.commonAncestorContainer;
        if (containerElement.nodeType != 1) {
            containerElement = containerElement.parentNode;
        }

        var treeWalker = document.createTreeWalker(
            containerElement,
            NodeFilter.SHOW_TEXT,
            // Note that Range.intersectsNode is non-standard but
            // implemented in WebKit
            function(node) {
                return rangeIntersectsNode(range, node) ?
                    NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
            },
            false
        );

        var selectedTextNodes = [];
        while (treeWalker.nextNode()) {
            selectedTextNodes.push(treeWalker.currentNode);
        }

        var textNode, span;

        // Place each text node within range inside a <span>
        // element with the desired class
        for (var i = 0, len = selectedTextNodes.length; i < len; ++i) {
            textNode = selectedTextNodes[i];
            span = document.createElement("span");
            span.className = cssClass + " " + uniqueCssClass;
            textNode.parentNode.insertBefore(span, textNode);
            span.appendChild(textNode);
        }

        return uniqueCssClass;
    }

    function removeSpansWithClass(cssClass) {
        var spans = document.body.getElementsByClassName(cssClass),
            span, parentNode;

        // Convert spans to an array to prevent live updating of
        // the list as we remove the spans
        spans = Array.prototype.slice.call(spans, 0);

        for (var i = 0, len = spans.length; i < len; ++i) {
            span = spans[i];
            parentNode = span.parentNode;
            parentNode.insertBefore(span.firstChild, span);
            parentNode.removeChild(span);

            // Glue any adjacent text nodes back together
            parentNode.normalize();
        }
    }

    var c;
</script>

<input type="button" onclick="c = applyClassToSelection('someclass')"
    value="Add class">
<input type="button" onclick="removeSpansWithClass(c)"
    value="Remove class">
2020-05-16