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 に答える
2

選択したノードの精度を向上させるために、MikeB の回答に基づいて 2 つの追加の修正を行いました。

複数の要素にまたがるテキストに沿ってカーソルをドラッグすることによって行われる範囲選択以外の、すべての選択操作でこれを特にテストしています。

Firefox では、[すべて選択] (CMD+A) を押すと、startContainer と endContainer が contenteditable div である範囲が返されます。違いは、最初と最後の子ノードのインデックスである startOffset と endOffset にあります。

Chrome では、[すべて選択] (CMD + A) を押すと、startContainer が contenteditable div の最初の子ノードであり、endContainer が contenteditable div の最後の子ノードである範囲が返されます。

私が追加した変更は、2 つの間の不一致を回避します。追加の説明については、コード内のコメントを参照してください。

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 に答える
0

ボブ。関数は startNode と endNode のみを返します。間にあるノードはアレイにプッシュされません。

while ループが getNextNode() で null を返すように見えるため、そのブロックは実行されません。

于 2016-06-10T05:46:53.237 に答える
0

アンノン、大活躍。私はオリジナルを修正し、以下に Stefan の修正を加えました。

さらに、関数を汎用アルゴリズムに変換して 2 つのノード間を移動する Range への依存を取り除きました。さらに、すべてを 1 つの関数にまとめました。

他の解決策についての考え:

  • 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 に答える