【问题标题】:Replace non-image elements in DOM tree with text in JS/jQuery用 JS/jQuery 中的文本替换 DOM 树中的非图像元素
【发布时间】:2011-08-22 08:42:08
【问题描述】:

我想用它们的文本替换contenteditable 中的非img 元素。但是,我想保留任何 img 元素,包括嵌套在其他元素中的元素。换句话说:

给定输入,例如:

<div><span>Foo <strong>Bar <img src="blah.png"></strong> and more text <img src="another.png"></span> With some other text <img src="yetmore.png"></div>

我想制作:

Foo Bar <img src="blah.png"> and more text <img src="another.png"> With some other text <img src="yetmore.png">

由于这是一个contenteditable,我不想使用innerHTML 读/写,因为这会丢失光标位置等(恢复它很困难,因为你最终会得到一个不同的 DOM 树,所以您的选择节点会丢失)。

我最好的选择是遍历树并手动拆分和连接文本节点等等吗?我希望有更好的方法,或者已经可以做这样的事情的库......

【问题讨论】:

    标签: javascript jquery dom contenteditable


    【解决方案1】:

    对根元素进行浅层克隆。沿着原始的元素树走,收集文本。当您遇到一个 img 元素时,将收集到的文本添加为​​作为克隆子节点的文本节点。附加图片。再次开始收集文本。

    一直到最后,然后将文档中的原始根元素替换为克隆。

    编辑

    类似:

    function toArray(o) {
      var a = [], i = o.length;
      while (i--) {
        a[i] = o[i];
      }
      return a;
    }
    
    function cleanUp(el) {
    
      var e = el.cloneNode(false);
    
      function addText(text) {
        if (text != '') {
          e.appendChild(document.createTextNode(text));
        }
      }
    
      function collectText(el) {
        var node, nodes = toArray(el.childNodes);
        var text = '';
    
        for (var i=0, iLen=nodes.length; i<iLen; i++) {
          node = nodes[i];
    
          if (node.tagName && node.tagName.toLowerCase() == 'img') {
            addText(text);
            e.appendChild(node);
            text = '';
    
          } else if (node.nodeType == 3) {
            text += node.data;
    
          } else if (node.nodeType == 1) {
            addText(text);
            text = '';
            collectText(node);
          }
        }
    
        if (text != '') {
          e.appendChild(document.createTextNode(text));
        }
      }
      collectText(el);
      el.parentNode.replaceChild(e, el);
    }
    

    【讨论】:

    • 我最终使用了这个解决方案 - 谢谢!我仍然需要稍微摆弄一下,让它将后续文本连接到一个文本节点中,即使它跨越嵌套元素。然后我仍然必须处理焦点/光标位置。幸运的是,就我而言,我只需要对粘贴进行这种清理,在这种情况下,焦点应该始终在替换内容的最后一个节点之后,这很容易以编程方式完成。
    • 哦,我最终创建了一个要插入的节点数组,然后调用了 insertBefore,最后在el 上删除Child,而不是 cloneNode...这样我可以有区别地使用它冒犯了我的 contenteditable 的 childNodes,这加快了我的代码速度并避免了为普通打字(正在运行相同的函数)进行代价高昂的 treewalking。
    【解决方案2】:

    进行 DOM 替换几乎肯定会丢失光标位置/选择,但仍然是正确的方法。我推荐Rangy 用于saving and restoring the selection,以及跨浏览器范围/选择处理(披露:我是Rangy 的作者)。

    这是一个删除非&lt;img&gt; 元素并在所有主要浏览器(包括IE 6)中保留先前选择/插入符号位置的示例。它递归地将主容器节点的&lt;img&gt; 和文本后代移动到DocumentFragment 中,并在最终将片段附加到现在为空的容器节点之前删除所有其他节点。它还规范化(即连接相邻的文本节点)。

    带有Rangy选择保存和恢复的jsFiddle:http://jsfiddle.net/CRLRj/1/

    元素移除代码:

    function removeNonImgElements(node) {
        var frag = document.createDocumentFragment();
    
        function move(node, moveSelf) {
            var type = node.nodeType, name = node.nodeName;
    
            // Deal with child nodes first
            var child;
            while ( (child = node.firstChild) ) {
                move(child, true);
            }
    
            if (!moveSelf) {
                return;
            }
    
            // Keep text, images and Rangy selection marker elements
            if (type == 1 && (name == "IMG" ||
                     (name == "SPAN" && /^selectionBoundary/.test(node.id)))) {
                frag.appendChild(node);
            } else if (type == 3) {
                var previousNode = frag.lastChild;
                if (previousNode && previousNode.nodeType == 3) {
                    // Concatenate text nodes rather than have two adjacent
                    previousNode.data = previousNode.data + node.data;
                    node.parentNode.removeChild(node);
                } else {
                    frag.appendChild(node);
                }
            } else {
                node.parentNode.removeChild(node);
            }
        }
    
        move(node, false);
        node.appendChild(frag);
    }
    

    【讨论】:

    • 我不会为 hasChildNodes 测试而烦恼,因为您正在删除节点,您可以在存在 firstChild 时继续进行。连接相邻文本节点的好主意。
    • @RobG:是的,我同意你的看法。我不知道为什么我把它放进去;我通常不会打扰。我会删除它。
    • 我 + 了这个,但最终选择了 Rob 的解决方案,因为添加 Rangy 对于我正在做的事情来说有点过于严厉(诚然,如果我一直这么说并最终得到解决问题的数千行代码,我可能应该重新评估 - 但我们还没有到那个时候。在我们的应用程序中,我们正在清理粘贴/keydown/mouseup/mousedown。这意味着焦点应该始终是在替换内容的最后一个节点之后,我只是在这样的清理之后将带有setStartAfter 的范围硬编码为window.getSelection。这在我的测试中似乎效果很好。
    • @Gijs:很公平。 Rangy 的主要用例是您是否支持 IE window.getSelection,我假设您不支持。话虽如此,Rangy 对上述功能的参与很少且很容易移除,但@RobG 的解决方案同样出色。
    【解决方案3】:
    $(document).ready(function(){
        replaceChildren($('#contenteditable'));
    });
    
    function replaceChildren(elem){
        $(elem).children("*").not("img").each(function(){
            if ($(this).children("*").not("img").length>0){
                replaceChildren(this);
            }
            $(this).after($(this).html());
            $(this).remove();
        });
    }
    

    某种递归解决方案。

    【讨论】:

    • after()调用中仍然使用innerHTML
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-04-22
    • 2014-01-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-06
    • 2018-12-16
    相关资源
    最近更新 更多