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

dra*_*ard 8 html javascript css iphone uiwebview

我在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中工作并向前发展.
  • 所有源文件都随应用程序一起提供.
  • 请详细说明.

谢谢你的任何建议.

Tim*_*own 19

我认为你要求很多东西来获得一个完整的解决方案,但它似乎很有趣所以我已经实现了它.以下适用于最近的WebKit浏览器,包括运行OS 3.0的iPhone上的Safari.它使用非标准但方便的intersectsNode方法Range,它存在于WebKit中,但在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">
Run Code Online (Sandbox Code Playgroud)