20

我正在尝试获取范围对象内的所有 DOM 节点,最好的方法是什么?

var selection = window.getSelection(); //what the user has selected
var range = selection.getRangeAt(0); //the first range of the selection
var startNode = range.startContainer;
var endNode = range.endContainer;
var allNodes = /*insert magic*/;

在过去的几个小时里,我一直在想一种方法,并想出了这个:

var getNextNode = function(node, skipChildren){
    //if there are child nodes and we didn't come from a child node
    if (node.firstChild && !skipChildren) {
        return node.firstChild;
    }
    if (!node.parentNode){
        return null;
    }
    return node.nextSibling 
        || getNextNode(node.parentNode, true);
};

var getNodesInRange = function(range){
    var startNode = range.startContainer.childNodes[range.startOffset]
            || range.startContainer;//it's a text node
    var endNode = range.endContainer.childNodes[range.endOffset]
            || range.endContainer;

    if (startNode == endNode && startNode.childNodes.length === 0) {
        return [startNode];
    };

    var nodes = [];
    do {
        nodes.push(startNode);
    }
    while ((startNode = getNextNode(startNode)) 
            && (startNode != endNode));
    return nodes;
};

但是,当结束节点是开始节点的父节点时,它会返回页面上的所有内容。我确定我忽略了一些明显的东西?或者也许以完全错误的方式去做。

MDC/DOM/范围

4

9 回答 9

16

这是我想出的解决此问题的实现:

function getNextNode(node)
{
    if (node.firstChild)
        return node.firstChild;
    while (node)
    {
        if (node.nextSibling)
            return node.nextSibling;
        node = node.parentNode;
    }
}

function getNodesInRange(range)
{
    var start = range.startContainer;
    var end = range.endContainer;
    var commonAncestor = range.commonAncestorContainer;
    var nodes = [];
    var node;

    // walk parent nodes from start to common ancestor
    for (node = start.parentNode; node; node = node.parentNode)
    {
        nodes.push(node);
        if (node == commonAncestor)
            break;
    }
    nodes.reverse();

    // walk children and siblings from start until end is found
    for (node = start; node; node = getNextNode(node))
    {
        nodes.push(node);
        if (node == end)
            break;
    }

    return nodes;
}
于 2011-10-28T14:58:41.840 回答
11

如果它是父节点,getNextNode 将递归地跳过您想要的 endNode。

改为在 getNextNode 内执行条件中断检查:

var getNextNode = function(node, skipChildren, endNode){
  //if there are child nodes and we didn't come from a child node
  if (endNode == node) {
    return null;
  }
  if (node.firstChild && !skipChildren) {
    return node.firstChild;
  }
  if (!node.parentNode){
    return null;
  }
  return node.nextSibling 
         || getNextNode(node.parentNode, true, endNode); 
};

在 while 语句中:

while (startNode = getNextNode(startNode, false , endNode));
于 2009-03-20T21:24:12.857 回答
3

Rangy有一个Range.getNodes([Array nodeTypes[, Function filter]])功能

于 2013-10-14T23:05:19.550 回答
2

我根据 MikeB 的回答做了 2 个额外的修复,以提高所选节点的准确性。

除了通过沿着跨越多个元素的文本拖动光标来进行范围选择之外,我特别在选择所有操作上对此进行了测试。

在 Firefox 中,点击全选 (CMD+A) 会返回一个范围,其中 startContainer 和 endContainer 是 contenteditable div,区别在于 startOffset 和 endOffset 分别是第一个和最后一个子节点的索引。

在 Chrome 中,点击全选 (CMD+A) 会返回一个范围,其中 startContainer 是 contenteditable div 的第一个子节点,endContainer 是 contenteditable div 的最后一个子节点。

我添加的修改解决了两者之间的差异。您可以查看代码中的注释以获取更多说明。

function getNextNode(node) {
    if (node.firstChild)
        return node.firstChild;

    while (node) {
        if (node.nextSibling) return node.nextSibling;
        node = node.parentNode;
    }
}

function getNodesInRange(range) {

    // MOD #1
    // When the startContainer/endContainer is an element, its
    // startOffset/endOffset basically points to the nth child node
    // where the range starts/ends.
    var start = range.startContainer.childNodes[range.startOffset] || range.startContainer;
    var end = range.endContainer.childNodes[range.endOffset] || range.endContainer;
    var commonAncestor = range.commonAncestorContainer;
    var nodes = [];
    var node;

    // walk parent nodes from start to common ancestor
    for (node = start.parentNode; node; node = node.parentNode)
    {
        nodes.push(node);
        if (node == commonAncestor)
            break;
    }
    nodes.reverse();

    // walk children and siblings from start until end is found
    for (node = start; node; node = getNextNode(node))
    {
        // MOD #2
        // getNextNode might go outside of the range
        // For a quick fix, I'm using jQuery's closest to determine
        // when it goes out of range and exit the loop.
        if (!$(node.parentNode).closest(commonAncestor)[0]) break;

        nodes.push(node);
        if (node == end)
            break;
    }

    return nodes;
};
于 2015-01-26T12:31:38.033 回答
1

下面的代码解决你的问题

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>payam jabbari</title>
<script src="http://code.jquery.com/jquery-2.0.2.min.js" type="text/javascript"></script>
<script type="text/javascript">

$(document).ready(function(){
    var startNode = $('p.first').contents().get(0);
var endNode = $('span.second').contents().get(0);
var range = document.createRange();
range.setStart(startNode, 0);
range.setEnd(endNode, 5);
var selection = document.getSelection();
selection.addRange(range);
// below code return all nodes in selection range. this code work in all browser
var nodes = range.cloneContents().querySelectorAll("*");
for(var i=0;i<nodes.length;i++)
{
   alert(nodes[i].innerHTML);
}
});
</script>
</head>

<body>
<div>

<p class="first">Even a week ago, the idea of a Russian military intervention in Ukraine seemed far-fetched if not totally alarmist. But the arrival of Russian troops in Crimea over the weekend has shown that he is not averse to reckless adventures, even ones that offer little gain. In the coming days and weeks</p>

<ol>
    <li>China says military will respond to provocations.</li>
    <li >This Man Has Served 20 <span class="second"> Years—and May Die—in </span> Prison for Marijuana.</li>
    <li>At White House, Israel's Netanyahu pushes back against Obama diplomacy.</li>
</ol>
</div>
</body>
</html>
于 2014-03-04T12:04:47.680 回答
1

我为此编写了完美的代码,它对每个节点都 100% 有效:

function getNodesInSelection() {
    
var range = window.getSelection().getRangeAt(0);
var node = range.startContainer;

var ranges = []
var nodes = []
        
while (node != null) {        
    
    var r = document.createRange();
    r.selectNode(node)
    
    if(node == range.startContainer){
        r.setStart(node, range.startOffset)
    }
    
    if(node == range.endContainer){
        r.setEnd(node, range.endOffset)
    }
    
    
    ranges.push(r)
    nodes.push(node)
    
    node = getNextElementInRange(node, range)
}
     
// do what you want with ranges and nodes
}

这里有一些辅助函数

function getClosestUncle(node) {

var parent = node.parentElement;

while (parent != null) {
    var uncle = parent.nextSibling;
    if (uncle != null) {
        return uncle;
    }
    
    uncle = parent.nextElementSibling;
    if (uncle != null) {
        return uncle;
    }
    
    parent = parent.parentElement
}

return null
}
 

                    
function getFirstChild(_node) {

var deep = _node

while (deep.firstChild != null) {
    
    deep = deep.firstChild
}

return deep
}
  

                    
function getNextElementInRange(currentNode, range) {

var sib = currentNode.nextSibling;

if (sib != null && range.intersectsNode(sib)) {
    return getFirstChild(sib)
}
        
var sibEl = currentNode.nextSiblingElemnent;

if (sibEl != null && range.intersectsNode(sibEl)) {
    return getFirstChild(sibEl)
}

var uncle = getClosestUncle(currentNode);
var nephew = getFirstChild(uncle)

if (nephew != null && range.intersectsNode(nephew)) {
    return nephew
}

return null
}
于 2020-08-18T06:53:25.483 回答
0

安农,干得好。我已经修改了原始版本,并在下面添加了 Stefan 的修改。

另外,我去掉了对 Range 的依赖,将函数转换为通用算法,在两个节点之间行走。另外,我将所有内容都包装到了一个函数中。

关于其他解决方案的想法:

  • 对依赖 jquery 不感兴趣
  • 使用 cloneNode 将结果提升到一个片段,这可以防止在过滤期间可能想要进行的许多操作。
  • 在克隆的片段上使用 querySelectAll 很不稳定,因为开始或结束节点可能位于包装节点内,因此解析器可能没有结束标记?

例子:

<div>
    <p>A</p>
    <div>
        <p>B</p>
        <div>
            <p>C</p>
        </div>
    </div>
</div>

假设起始节点是“A”段落,结束节点是“C”段落。生成的克隆片段将是:

<p>A</p>
    <div>
        <p>B</p>
        <div>
            <p>C</p>

我们缺少结束标签?导致时髦的DOM结构?

无论如何,这里的函数,它包括一个过滤器选项,它应该返回 TRUE 或 FALSE 以包含/排除结果。

var getNodesBetween = function(startNode, endNode, includeStartAndEnd, filter){
    if (startNode == endNode && startNode.childNodes.length === 0) {
        return [startNode];
    };

    var getNextNode = function(node, finalNode, skipChildren){
        //if there are child nodes and we didn't come from a child node
        if (finalNode == node) {
            return null;
        }
        if (node.firstChild && !skipChildren) {
            return node.firstChild;
        }
        if (!node.parentNode){
            return null;
        }
        return node.nextSibling || getNextNode(node.parentNode, endNode, true);
    };

    var nodes = [];

    if(includeStartAndEnd){
        nodes.push(startNode);
    }

    while ((startNode = getNextNode(startNode, endNode)) && (startNode != endNode)){
        if(filter){
            if(filter(startNode)){
                nodes.push(startNode);
            }
        } else {
            nodes.push(startNode);
        }
    }

    if(includeStartAndEnd){
        nodes.push(endNode);
    }

    return nodes;
};
于 2015-06-25T21:46:45.343 回答
0

鲍勃 该函数仅返回 startNode 和 endNode。中间的节点不会被推送到数组中。

似乎 while 循环在 getNextNode() 上返回 null,因此该块永远不会被执行。

于 2016-06-10T05:46:53.237 回答
0

这是函数返回子范围数组

function getSafeRanges(range) {

var doc = document;

var commonAncestorContainer = range.commonAncestorContainer;
var startContainer = range.startContainer;
var endContainer = range.endContainer;
var startArray = new Array(0),
    startRange = new Array(0);
var endArray = new Array(0),
    endRange = new Array(0);
// @@@@@ If start container and end container is same
if (startContainer == endContainer) {
    return [range];
} else {
    for (var i = startContainer; i != commonAncestorContainer; i = i.parentNode) {
        startArray.push(i);
    }
    for (var i = endContainer; i != commonAncestorContainer; i = i.parentNode) {
        endArray.push(i);
    }
}
if (0 < startArray.length) {
    for (var i = 0; i < startArray.length; i++) {
        if (i) {
            var node = startArray[i - 1];
            while ((node = node.nextSibling) != null) {
                startRange = startRange.concat(getRangeOfChildNodes(node));
            }
        } else {
            var xs = doc.createRange();
            var s = startArray[i];
            var offset = range.startOffset;
            var ea = (startArray[i].nodeType == Node.TEXT_NODE) ? startArray[i] : startArray[i].lastChild;
            xs.setStart(s, offset);
            xs.setEndAfter(ea);
            startRange.push(xs);
        }
    }
}
if (0 < endArray.length) {
    for (var i = 0; i < endArray.length; i++) {
        if (i) {
            var node = endArray[i - 1];
            while ((node = node.previousSibling) != null) {
                endRange = endRange.concat(getRangeOfChildNodes(node));
            }
        } else {
            var xe = doc.createRange();
            var sb = (endArray[i].nodeType == Node.TEXT_NODE) ? endArray[i] : endArray[i].firstChild;
            var end = endArray[i];
            var offset = range.endOffset;
            xe.setStartBefore(sb);
            xe.setEnd(end, offset);
            endRange.unshift(xe);
        }
    }
}
var topStartNode = startArray[startArray.length - 1];
var topEndNode = endArray[endArray.length - 1];
var middleRange = getRangeOfMiddleElements(topStartNode, topEndNode);
startRange = startRange.concat(middleRange);
response = startRange.concat(endRange);
return response;

}

于 2017-06-14T15:41:39.280 回答