如何使用HTML标签包装跨界DOM选择范围?

问题描述 投票:15回答:3

目前,我正在通过s = window.getSelection()range = s.getRangeAt(0)捕获用户的文本选择(浏览器的提示不留)。每当在[[0]之内]进行选择<p>时,我都可以轻松地调用range.surroundContents(document.createElement("em"))以使所选择的文本带有<em>标记wrapped但是,在此示例中,>

<p>This is the Foo paragraph.</p> <p>This is the Bar paragraph.</p> <p>This is the Baz paragraph.</p>

[当用户从FooBaz进行文本选择时,我无法调用range.surroundContents:Firefox失败The boundary-points of a range does not meet specific requirements." code: "1,因为选择无效的HTML。

在那种情况下,我想以某种方式在DOM中获得以下状态:

<p>This is the <em>Foo paragraph.</em></p> <p><em>This is the Bar paragraph.</em></p> <p><em>This is the Baz</em> paragraph.</p>

有什么想法吗?


FYI:我一直在尝试使用Range API,但看不到实现该结果的直接方法。带有

var r = document.createRange(); r.setStart(range.startContainer, range.startOffset); r.setEnd(range.endContainer, range.endOffset+40); selection.addRange(r);

我最终可以通过重新定位偏移量来破解某些东西,但仅适用于'start'和'end'容器! (即,在这种情况下是Bar段,如何包装?)

现在,我正在通过s = window.getSelection()和range = s.getRangeAt(0)捕获用户的文本选择(浏览器不显示)。每当在

内进行选择时,我都可以轻松地调用...

] >>

您是否尝试过以下方法(实际上是W3C规范中对SurroundContents应该做什么的描述):

var wrappingNode = document.createElement("div"); wrappingNode.appendChild(range.extractContents()); range.insertNode(wrappingNode);

我目前正在使用内联编辑器,并且我编写了一个函数,该函数可以像execCommand一样用任何类型的元素正确包装跨元素范围。

function surroundSelection(elementType) { function getAllDescendants (node, callback) { for (var i = 0; i < node.childNodes.length; i++) { var child = node.childNodes[i]; getAllDescendants(child, callback); callback(child); } } function glueSplitElements (firstEl, secondEl){ var done = false, result = []; if(firstEl === undefined || secondEl === undefined){ return false; } if(firstEl.nodeName === secondEl.nodeName){ result.push([firstEl, secondEl]); while(!done){ firstEl = firstEl.childNodes[firstEl.childNodes.length - 1]; secondEl = secondEl.childNodes[0]; if(firstEl === undefined || secondEl === undefined){ break; } if(firstEl.nodeName !== secondEl.nodeName){ done = true; } else { result.push([firstEl, secondEl]); } } } for(var i = result.length - 1; i >= 0; i--){ var elements = result[i]; while(elements[1].childNodes.length > 0){ elements[0].appendChild(elements[1].childNodes[0]); } elements[1].parentNode.removeChild(elements[1]); } } // abort in case the given elemenType doesn't exist. try { document.createElement(elementType); } catch (e){ return false; } var selection = getSelection(); if(selection.rangeCount > 0){ var range = selection.getRangeAt(0), rangeContents = range.extractContents(), nodesInRange = rangeContents.childNodes, nodesToWrap = []; for(var i = 0; i < nodesInRange.length; i++){ if(nodesInRange[i].nodeName.toLowerCase() === "#text"){ nodesToWrap.push(nodesInRange[i]); } else { getAllDescendants(nodesInRange[i], function(child){ if(child.nodeName.toLowerCase() === "#text"){ nodesToWrap.push(child); } }); } }; for(var i = 0; i < nodesToWrap.length; i++){ var child = nodesToWrap[i], wrap = document.createElement(elementType); if(child.nodeValue.replace(/(\s|\n|\t)/g, "").length !== 0){ child.parentNode.insertBefore(wrap, child); wrap.appendChild(child); } else { wrap = null; } } var firstChild = rangeContents.childNodes[0]; var lastChild = rangeContents.childNodes[rangeContents.childNodes.length - 1]; range.insertNode(rangeContents); glueSplitElements(firstChild.previousSibling, firstChild); glueSplitElements(lastChild, lastChild.nextSibling); rangeContents = null; } };

这是一个JSFiddle,带有一些复杂的HTML作为演示:http://jsfiddle.net/mjf9K/1/。请注意,我直接从应用程序中删除了此内容。我使用一些帮助程序将范围正确恢复到原始选择等。这些不包括在内。

即在将contentEditable = true属性添加到这些段落的父项时,选择任何文本,甚至跨段落,然后进行调用

document.execCommand('italic', false, null);

最后,如果需要,将contentEditable属性设置为false。

顺便说一句,这也适用于IE,但要进入可编辑模式,我认为它被称为designMode或类似的东西,用google搜索。

javascript html dom
3个回答
9
投票
您是否尝试过以下方法(实际上是W3C规范中对SurroundContents应该做什么的描述):

var wrappingNode = document.createElement("div"); wrappingNode.appendChild(range.extractContents()); range.insertNode(wrappingNode);


3
投票
我目前正在使用内联编辑器,并且我编写了一个函数,该函数可以像execCommand一样用任何类型的元素正确包装跨元素范围。

function surroundSelection(elementType) { function getAllDescendants (node, callback) { for (var i = 0; i < node.childNodes.length; i++) { var child = node.childNodes[i]; getAllDescendants(child, callback); callback(child); } } function glueSplitElements (firstEl, secondEl){ var done = false, result = []; if(firstEl === undefined || secondEl === undefined){ return false; } if(firstEl.nodeName === secondEl.nodeName){ result.push([firstEl, secondEl]); while(!done){ firstEl = firstEl.childNodes[firstEl.childNodes.length - 1]; secondEl = secondEl.childNodes[0]; if(firstEl === undefined || secondEl === undefined){ break; } if(firstEl.nodeName !== secondEl.nodeName){ done = true; } else { result.push([firstEl, secondEl]); } } } for(var i = result.length - 1; i >= 0; i--){ var elements = result[i]; while(elements[1].childNodes.length > 0){ elements[0].appendChild(elements[1].childNodes[0]); } elements[1].parentNode.removeChild(elements[1]); } } // abort in case the given elemenType doesn't exist. try { document.createElement(elementType); } catch (e){ return false; } var selection = getSelection(); if(selection.rangeCount > 0){ var range = selection.getRangeAt(0), rangeContents = range.extractContents(), nodesInRange = rangeContents.childNodes, nodesToWrap = []; for(var i = 0; i < nodesInRange.length; i++){ if(nodesInRange[i].nodeName.toLowerCase() === "#text"){ nodesToWrap.push(nodesInRange[i]); } else { getAllDescendants(nodesInRange[i], function(child){ if(child.nodeName.toLowerCase() === "#text"){ nodesToWrap.push(child); } }); } }; for(var i = 0; i < nodesToWrap.length; i++){ var child = nodesToWrap[i], wrap = document.createElement(elementType); if(child.nodeValue.replace(/(\s|\n|\t)/g, "").length !== 0){ child.parentNode.insertBefore(wrap, child); wrap.appendChild(child); } else { wrap = null; } } var firstChild = rangeContents.childNodes[0]; var lastChild = rangeContents.childNodes[rangeContents.childNodes.length - 1]; range.insertNode(rangeContents); glueSplitElements(firstChild.previousSibling, firstChild); glueSplitElements(lastChild, lastChild.nextSibling); rangeContents = null; } };


1
投票
即在将contentEditable = true属性添加到这些段落的父项时,选择任何文本,甚至跨段落,然后进行调用

document.execCommand('italic', false, null);


0
投票
由于Rangy库,这非常简单,它包含一个“ createClassApplier”模块,如果需要,可以使用多个换行包装任何跨边界选择。

您可以通过以下方式获得它:

npm install rangy

© www.soinside.com 2019 - 2024. All rights reserved.