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

IT技术 javascript iphone html css uiwebview
2021-03-03 16:59:44

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

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

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

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

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

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

一些注意事项:

  • 请直接使用 javascript,没有像 jquery 这样的外部框架。
  • 更改永远不需要写入磁盘。
  • 更改应该是可撤销的或至少是可移除的。
  • 要应用的样式已存在于 css 文件中。
  • 它需要在 iPhone 3.0 及更高版本中工作。
  • 所有源文件都随应用程序一起提供。
  • 请详细点。

感谢您的任何建议。

1个回答

我认为您对此提出了很多要求以获得完整的解决方案,但它看起来很有趣所以我已经实现了它。以下适用于最近的 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">
也许把这个放在github上,这样我们至少可以遵循。
2021-05-01 16:59:44
@Mark、@user306253、@Glasswing、@jozsef:我已经用一个module更新了我的库,该module可以在所有浏览器(包括 IE)中执行此操作:code.google.com/p/rangy
2021-05-07 16:59:44
谢谢你。这超出了我的预期。
2021-05-13 16:59:44
我正在开发一个跨浏览器范围/选择库,其中将包含一个改进版本。进展不大,但我已经创建了一个 Google Code 项目:code.google.com/p/rangy
2021-05-21 16:59:44