【问题标题】:How to wrap with HTML tags a cross-boundary DOM selection range?如何用 HTML 标签包装跨界 DOM 选择范围?
【发布时间】:2009-11-13 18:18:22
【问题描述】:

现在我正在通过s = window.getSelection()range = s.getRangeAt(0) 捕获用户的文本选择(除了浏览器的实现)。每当在<p>进行选择时,我都可以轻松调用range.surroundContents(document.createElement("em")) 以使用<em> 标记包装所选文本。

然而,在这个例子中,

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

有什么想法吗?


仅供参考:我一直在尝试使用Range API,但我看不到实现该结果的直接方法。与

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

我最终可以通过重新定位偏移量来破解某些东西,但仅限于“开始”和“结束”容器! (即在这种情况下Bar 段落,我该如何包装它?)

【问题讨论】:

  • javascript中最令人沮丧的事情之一,至少我发现了……希望你能找到一个好的答案!
  • 是的,我确认。这是一场噩梦
  • +1 向权力讲真话
  • 在此处使用“Rangy”库查看答案:stackoverflow.com/questions/5765381/…
  • 仍然面临这个噩梦:(

标签: javascript html dom


【解决方案1】:

您是否尝试过以下方法(这实际上是 W3C 规范中关于环绕内容应该做什么的描述):

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

【讨论】:

  • 感谢@nlazarov 的提示。我已经将它提取到一个独立的 CommonJS 模块 wrap-range: github.com/webmodules/wrap-range
  • 但是 OP 要求“用 HTML 标签包装一个跨界 DOM 选择范围”
  • @nlazarov - 神圣的螃蟹,先生,您是救生员!被环绕内容()函数卡住了一天,我无法破解它。
【解决方案2】:

我目前正在开发一个内联编辑器,并且我已经编写了一个函数,该函数可以像 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;
    }
};

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

【讨论】:

  • 当 lastElement 为 &amp;nbsp; 时,它很遗憾地失败了
【解决方案3】:

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

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

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

顺便说一句,这也适用于 IE,除了进入可编辑模式我认为它被称为 designMode 什么的,谷歌它。

【讨论】:

  • 很好的答案-但是:我已经尝试过这个,它对我来说并不真正有用,因为需要访问 位置/偏移量以获得其他东西。除非您知道如何使用 contentEditable 来获取它们?还是谢谢
  • 好吧,如果你不是很喜欢 但可以使用 标签来完成你想做的事情,那么下面的方法就可以了:document.execCommand('styleWithCSS', false , 错误的); document.execCommand('italic', false, null);
猜你喜欢
  • 2015-06-20
  • 1970-01-01
  • 1970-01-01
  • 2010-12-07
  • 2011-01-29
  • 2018-02-16
  • 1970-01-01
  • 2023-03-25
  • 1970-01-01
相关资源
最近更新 更多