How to get nodes lying inside a range with javascr

2019-01-07 12:45发布

I'm trying to get all the DOM nodes that are within a range object, what's the best way to do this?

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*/;

I've been been thinking of a way for the last few hours and came up with this:

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;
};

However when the end node is the parent of the start node it returns everything on the page. I'm sure I'm overlooking something obvious? Or maybe going about it in totally the wrong way.

MDC/DOM/range

8条回答
我欲成王,谁敢阻挡
2楼-- · 2019-01-07 13:12

here is function return you array of sub-ranges

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;

}

查看更多
甜甜的少女心
3楼-- · 2019-01-07 13:15

Annon, great work. I've modified the original plus included Stefan's modifications in the following.

In addition, I removed the reliance on Range, which converts the function into an generic algorithm to walk between two nodes. Plus, I've wrapped everything into a single function.

Thoughts on other solutions:

  • Not interested in relying on jquery
  • Using cloneNode lifts the results to a fragment, which prevents many operations one might want to conduct during filtering.
  • Using querySelectAll on a cloned fragment is wonky because the start or end nodes may be within a wrapping node, hence the parser may not have the closing tag?

Example:

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

Assume start node is the "A" paragraph, and the end node is the "C" paragraph . The resulting cloned fragment would be:

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

and we're missing closing tags? resulting in funky DOM structure?

Anyway, here's the function, which includes a filter option, which should return TRUE or FALSE to include/exclude from results.

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;
};
查看更多
登录 后发表回答