JS:获取 contentEditable div 中所有选定节点的数组

IT技术 javascript html wysiwyg
2021-03-16 19:13:38

嗨,我已经使用 contentEditable 有一段时间了,我认为我对它有很好的掌握。回避我的一件事是如何获取对部分或完全在用户选择范围内的所有节点的引用数组。有人有想法吗?

可以从以下几点开始:

<!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" />
<script type="text/javascript">
function getSelectedNodes(){
    var sel = window.getSelection();
    try{var frag=sel.getRangeAt(0).cloneContents()}catch(e){return(false);}
    var tempspan = document.createElement("span");
    tempspan.appendChild(frag);

    var selnodes = Array() //<<- how do I fill this array??
    var output = ''
    for(i in selnodes){
        output += "A "+selnodes[i].tagName+" was found\n"
        //do something cool with each element here...
    }
    return(output)
}
</script>
</head>

<body contentEditable="true" onkeypress="return(keypress(event))">
<div>This <strong>div</strong> is <em>content</em> <span class='red'>editable</span> and has a couple of <em><strong>child nodes</strong></em> within it</div>
<br />
<br />
<a href="#" onmouseover="alert(getSelectedNodes())">hover here</a>
</body>
</html>
3个回答

这是一个为您提供实际选择和部分选择的节点而不是克隆的版本。或者你可以使用我的Rangy库,它有一个getNodes()Range 对象方法并且可以在 IE < 9 中工作。

function nextNode(node) {
    if (node.hasChildNodes()) {
        return node.firstChild;
    } else {
        while (node && !node.nextSibling) {
            node = node.parentNode;
        }
        if (!node) {
            return null;
        }
        return node.nextSibling;
    }
}

function getRangeSelectedNodes(range) {
    var node = range.startContainer;
    var endNode = range.endContainer;

    // Special case for a range that is contained within a single node
    if (node == endNode) {
        return [node];
    }

    // Iterate nodes until we hit the end container
    var rangeNodes = [];
    while (node && node != endNode) {
        rangeNodes.push( node = nextNode(node) );
    }

    // Add partially selected nodes at the start of the range
    node = range.startContainer;
    while (node && node != range.commonAncestorContainer) {
        rangeNodes.unshift(node);
        node = node.parentNode;
    }

    return rangeNodes;
}

function getSelectedNodes() {
    if (window.getSelection) {
        var sel = window.getSelection();
        if (!sel.isCollapsed) {
            return getRangeSelectedNodes(sel.getRangeAt(0));
        }
    }
    return [];
}
蒂姆,我如何修改数组中的节点?我想通过使用 outerHTML(删除第一个 p 节点的 </p> 和第二个 p 节点的 <p> )来合并两个特定的 p 节点,但它不起作用。
2021-04-26 19:13:38
这太棒了!!!!!我已经抓住了我的头,因为我必须使用 cloneContents() 并且会丢失对 DOM 的引用。这是金蒂姆。
2021-05-08 19:13:38

你离得那么近!当您将 附加Document Fragment到临时span元素时,您已经将它们变成了一个可管理的组,可通过可信childNodes数组访问

    var selnodes = tempspan.childNodes;

此外,你在和自己的一些麻烦与for(i in selnodes)循环,这将使该元素的数组中,PLUSlength属性和__proto__ 特性,以及任何其他属性的对象可能有。

for在循环对象中的属性时,您实际上应该只使用这些类型的循环,然后始终使用 withif (obj.hasOwnProperty[i])过滤掉从原型继承的属性。

循环遍历数组时,请使用:

    for(var i=0,u=selnodes.length;i<u;i++)

最后,一旦您加载了该数组,您实际上需要先检查每个元素以查看它是 DOM 节点还是Text 节点,然后才能对其进行处理。我们可以通过检查它是否支持该tagName属性来做到这一点

    if (typeof selnodes[i].tagName !== 'undefined')

这是整件事:

<!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" />
<script type="text/javascript">
function getSelectedNodes(){
    var sel = window.getSelection();
    try{var frag=sel.getRangeAt(0).cloneContents()}catch(e){return(false);}
    var tempspan = document.createElement("span");
    tempspan.appendChild(frag);
    console.log(tempspan);
    window.selnodes = tempspan.childNodes;
    var output = ''
    for(var i=0, u=selnodes.length;i<u;i++){
        if (typeof selnodes[i].tagName !== 'undefined'){
          output += "A "+selnodes[i].tagName+" was found\n"
        }
        else output += "Some text was found: '"+selnodes[i].textContent+"'\n";
        //do something cool with each element here...
    }
    return(output)
}
</script>
</head>

<body contentEditable="true" onkeypress="return(keypress(event))">
<div>This <strong>div</strong> is <em>content</em> <span class='red'>editable</span> and has a couple of <em><strong>child nodes</strong></em> within it</div>
<br />
<br />
<a href="#" onmouseover="alert(getSelectedNodes())">hover here</a>
</body>
</html>
哇我真的很接近!childNodes 真的返回 DOM 真实节点的数组吗?我无法获得处理数组内容的标准属性。例如:selnodes[i].innerHTML = 'P:'+selnodes[i].innerHTML
2021-04-26 19:13:38
啊 - 所以这是返回 tempspan 中的节点。如何引用原始节点以便更改其内容?
2021-05-04 19:13:38
@cronoklee 您可以访问该range.commonAncestorContainer节点并遍历其子节点,直到找到range.startContainerrange.endContainerdeveloper.mozilla.org/en-US/docs/Web/API/Range
2021-05-04 19:13:38

下面的代码是解决您问题的示例,下面的代码返回范围内的所有选定节点

<!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>