【问题标题】:How to wrap text inside multiple nodes with a html tag如何使用 html 标签将文本包装在多个节点内
【发布时间】:2018-09-08 23:40:01
【问题描述】:

我正在使用 contenteditable 并尝试创建一个简单的编辑器。不幸的是我不能使用document.execCommand() 并且必须自己实现它。我在这里要做的是,如果用户按下粗体按钮,我想让文本变为粗体。 The code I have written below works, but it only works when the selection is in one node, not multiple nodes.

document.getElementById("bold").onclick = function() {
  var selection = document.getSelection(),
      range = selection.getRangeAt(0).cloneRange();
  range.surroundContents(document.createElement("b"));
  selection.removeAllRanges();
  selection.addRange(range);
}
<div contenteditable="true" id="div">This is the editor. If you embolden only **this**, it will work. But if you try to embolden **this <i>and this**</i>, it will not work because they are in different nodes</div>
<button id="bold">Bold</button>

我的问题是:有没有一种解决方案,我可以单击粗体,即使它们位于不同的节点中,它也可以使文本加粗?如果是这样,我该怎么做?我正在寻找简单而优雅的东西,但如果它必须很复杂,我会很感激对代码的一些解释。非常感谢。

【问题讨论】:

    标签: javascript html contenteditable


    【解决方案1】:

    这既不简单也不优雅,但按预期工作,没有额外的标记,是我能想到的最好的。

    基本上,你必须遍历与选择范围相交的dom树,并在遍历过程中收集仅由文本节点组成的子范围。

    查看Range documentation 以获取有关startContainerendContainer 的信息。 简而言之,它们在选择单个文本节点时是相同的,否则它们会为您提供遍历的起点和终点。

    收集完这些范围后,您可以将它们包装在您喜欢的标签中。

    它工作得很好,但不幸的是我无法在加粗后保留初始选择(用selection.setRange(..) 尝试了一切,但没有运气):

    document.getElementById("bold").onclick = function() {
        var selection = document.getSelection(),
        range = selection.getRangeAt(0).cloneRange();
        // start and end are always text nodes
        var start = range.startContainer;
        var end = range.endContainer;
        var ranges = [];
    
        // if start === end then it's fine we have selected a portion of a text node
    	while (start !== end) {
    
            var startText = start.nodeValue;
            var currentRange = range.cloneRange();
            // pin the range at the end of this text node
            currentRange.setEnd(start, startText.length);
            // keep the range for later
            ranges.push(currentRange);
    
            var sibling = start;
            do {
                if (sibling.hasChildNodes()) {
                    // if it has children then it's not a text node, go deeper
                    sibling = sibling.firstChild;
                } else if (sibling.nextSibling) {
                    // it has a sibling, go for it
                    sibling = sibling.nextSibling;
                } else {
                    // we're into a corner, we have to go up one level and go for next sibling
                    while (!sibling.nextSibling && sibling.parentNode) {
                        sibling = sibling.parentNode;
                    }
                    if (sibling) {
                        sibling = sibling.nextSibling;
                    }
                }
            } while (sibling !== null && sibling.nodeValue === null);
            if (!sibling) {
                // out of nodes!
                break;
            }
            // move range start to the identified next text node (sibling)
            range.setStart(sibling, 0);
            start = range.startContainer;
    	}
        // surround all collected range by the b tag
        for (var i = 0; i < ranges.length; i++) {
            var currentRange = ranges[i];
      	    currentRange.surroundContents(document.createElement("b"));
        }
        // surround the remaining range by a b tag
        range.surroundContents(document.createElement("b"));
    
        // unselect everything because I can't presere the original selection
        selection.removeAllRanges();
    
    }
    <div contenteditable="true" id="div">This is the editor. If you embolden only **this**, it will work. If you try <font color="red">to embolden **this <i>and this**</i>, it will work <font color="green">because</font> we are traversing the</font> nodes<table rules="all">
    <tr><td>it</td><td>will<td>even</td><td>work</td></tr>
    <tr><td>in</td><td>more</td><td>complicated</td><td><i>markup</i></td></tr>
    </table></div>
    <button id="bold">Bold</button>

    【讨论】:

    • 嘿,谢谢你的回答!使用这种方法有什么严重的缺点吗?虽然不是“简单/优雅”,但还是“好”的代码吗?
    • 这个问题很复杂,所以我希望有更好的解决方案。这显然没有经过全面测试,可能会在边缘情况下失败,但我介绍了一些。它的好处是对一个人来说是非递归的,即使它不太可能必须处理非常深的标记。只要确保它不会通过使用一个计数无限循环,如果迭代次数太高(故障安全)会导致循环中断。我看到的另一个问题是它会为已经加粗的文本生成不需要的 b 标签。
    • 好的,非常感谢。你帮了很多忙,你把这一切都解释得很好。我对你感激不尽!我会确保测试边缘情况。
    【解决方案2】:

    function makeItBold() {
    	const x = document.querySelectorAll(".selected");
    	for (let i = 0; i < x.length; i++) {
        	x[i].style.fontWeight = "bold"
    	}
    }
    
    function makeItNormal() {
    	const x = document.querySelectorAll(".selected");
    	for (let i = 0; i < x.length; i++) {
        	x[i].style.fontWeight = "normal"
            x[i].style.fontStyle = "normal"
    	}
    }
    
    function makeItItalic() {
    	const x = document.querySelectorAll(".selected");
    	for (let i = 0; i < x.length; i++) {
        	x[i].style.fontStyle = "italic"
    	}
    }
    <!DOCTYPE html>
    <html>
    <body>
    
    <h4>A simple demonstration:</h4>
    
    <button onclick="makeItBold()">Bold</button>
    <button onclick="makeItNormal()">Normal</button>
    <button onclick="makeItItalic()">Italic</button>
    
    <p class="selected">Test me out</p>
    
    </body>
    </html>

    【讨论】:

    • 这并不能以任何方式回答问题。问题是关于跨多个节点的文本。
    • 哇 - 首先,我不是要给你一个反对意见,而是答案,它错过了问题的一个基本点 - 跨多个节点执行此操作需要一种完全不同的方法。答案中也没有解释,这对于 .selected 类尤其需要。此外,您享有很高的声誉,因此反对票不会以任何方式影响您的 SO。无论如何 - 如果你那么在意反对票,我会删除它,请告诉我。
    猜你喜欢
    • 2022-01-09
    • 1970-01-01
    • 1970-01-01
    • 2021-02-22
    • 1970-01-01
    • 1970-01-01
    • 2011-06-28
    • 1970-01-01
    相关资源
    最近更新 更多