如何在JavaScript中遍历所选范围内的每个节点?

18
在javascript中实现富文本编辑器时,我需要对选择范围内的每个文本节点应用一些更改。Range对象提供了接口来获取所选范围的 startContainer, endContainer, startOffset, endOffset。如何迭代在它们之间的每个DOM节点?
var selection = window.getSelection();
var range = selection.getRange(0);
// How can I iterate over every node within the range?

2
访问范围DOM的示例是否让你快速掌握了它? - Rogier Spieker
1
@RogierSpieker 这对我来说指明了正确的方向,谢谢! - NeoWang
2个回答

17

建议使用NodeIterator来遍历range.commonAncestorContainer内部。

以下是示例代码:

var _iterator = document.createNodeIterator(
    range.commonAncestorContainer,
    NodeFilter.SHOW_ALL, // pre-filter
    {
        // custom filter
        acceptNode: function (node) {
            return NodeFilter.FILTER_ACCEPT;
        }
    }
);

var _nodes = [];
while (_iterator.nextNode()) {
    if (_nodes.length === 0 && _iterator.referenceNode !== range.startContainer) continue;
    _nodes.push(_iterator.referenceNode);
    if (_iterator.referenceNode === range.endContainer) break;
}

你应该使用NodeFilter.SHOW_ALL,因为你的范围可能包含多个nodeTypes。如果你知道你选择的是什么,你可以查看this reference来正确选择NodeFilter

编辑:我还想指出document.createTreeWalker()

关键区别在于document.createTreeWalker()允许您的acceptNode过滤器返回NodeFilter.FILTER_REJECTNodeFilter.FILTER_SKIP,并具有真正的差异。

来自NodeFilter文档的引用:

FILTER_REJECT:

当应该拒绝节点时,NodeFilter.acceptNode()方法返回的值。对于TreeWalker,子节点也将被拒绝。对于NodeIterator,此标志与FILTER_SKIP同义。

附言:NodeFilter.FILTER_REJECTNodeFilter.acceptNode()文档是不正确的。


我可能错了,但是我认为不使用 NodeFilter.SHOW_ALL 可能会导致第一个条件始终计算为 false,因为如果被过滤掉,则 referenceNode 可能永远不等于 startContainer。 - Benny Bottema

6

range.commonAncestorContainer会给你包含范围的节点。如果它给你一个文本节点,那么这就是你范围内唯一的节点。

如果它给你一个元素,你可以使用NodeIteratorel.querySelectorAll('*')来获取其中的节点。

并不是所有这些节点都在你的范围内,所以使用range.intersectsNode(el)来确认。


1
Range.intersectsNode()在IE浏览器(包括11及以下版本)中不受支持 :( 同样,Selection.containsNode()也是如此。 - simon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接