【问题标题】:javascript contentEditable - wrap cross-tag selectionsjavascript contentEditable - 包装跨标签选择
【发布时间】:2015-06-20 10:12:00
【问题描述】:

我正在用contentEditable做一些实验,遇到了这个问题:我有以下js sn-p

var range = document.getSelection().getRangeAt(0);
var newNode = document.createElement("span");
newNode.className = "customStyle";
range.surroundContents(newNode);

还有这个 HTML 片段:

<ul>
    <li>the <b>only entry</b> of the list</li>
</ul>
<p>Some text here in paragraph</p>

js 代码允许用&lt;span&gt; 标签包装当前选择。

It works perfectly when the selection includes whole HTML tags (eg selecting 'the only entry of') but not, of course, when the selection includes only one of their endings (eg selecting from 'entry' to 'Some',两者都包括在内)。

虽然我知道这个问题并非微不足道,但我正在寻找有关最佳方法的建议。提前致谢!

【问题讨论】:

  • 看看这个问题的答案:stackoverflow.com/questions/5765381/…
  • 您希望发生什么?您不能在与打开标签不同的父标签中结束标签...
  • 从 'entry' 到 'Some' 选择会导致类似 &lt;b&gt;&lt;span class="customStyle"&gt;entry&lt;/span&gt;&lt;/b&gt; &lt;span class="customStyle"&gt;of the list&lt;/span&gt;&lt;/li&gt; &lt;/ul&gt; &lt;p&gt;&lt;span class="customStyle"&gt;Some&lt;/span&gt;&lt;/p&gt; 的结果。 Tim Down 的答案和解决方案会做得很好!

标签: javascript range selection contenteditable rich-text-editor


【解决方案1】:

如果您只对包装文本部分感兴趣,基本方法是:

  • 获取选择范围
  • 对于每个范围边界,如果它位于文本节点的中间,则需要在边界处将文本节点一分为二并更新范围的边界,以使范围保持原位(example code from Rangy)李>
  • 获取范围内的所有文本节点(example code
  • &lt;span&gt; 元素包围每个文本节点
  • 重新选择范围

这是我 Rangy 库的class applier module 采用的方法。

我创建了一个示例,主要使用改编自 Rangy 的代码:

function getNextNode(node) {
    var next = node.firstChild;
    if (next) {
        return next;
    }
    while (node) {
        if ( (next = node.nextSibling) ) {
            return next;
        }
        node = node.parentNode;
    }
}

function getNodesInRange(range) {
    var start = range.startContainer;
    var end = range.endContainer;
    var commonAncestor = range.commonAncestorContainer;
    var nodes = [];
    var node;

    // Walk parent nodes from start to common ancestor
    for (node = start.parentNode; node; node = node.parentNode) {
        nodes.push(node);
        if (node == commonAncestor) {
            break;
        }
    }
    nodes.reverse();

    // Walk children and siblings from start until end is found
    for (node = start; node; node = getNextNode(node)) {
        nodes.push(node);
        if (node == end) {
            break;
        }
    }

    return nodes;
}

function getNodeIndex(node) {
    var i = 0;
    while ( (node = node.previousSibling) ) {
        ++i;
    }
    return i;
}

function insertAfter(node, precedingNode) {
    var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode;
    if (nextNode) {
        parent.insertBefore(node, nextNode);
    } else {
        parent.appendChild(node);
    }
    return node;
}

// Note that we cannot use splitText() because it is bugridden in IE 9.
function splitDataNode(node, index) {
    var newNode = node.cloneNode(false);
    newNode.deleteData(0, index);
    node.deleteData(index, node.length - index);
    insertAfter(newNode, node);
    return newNode;
}

function isCharacterDataNode(node) {
    var t = node.nodeType;
    return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment
}

function splitRangeBoundaries(range) {
    var sc = range.startContainer, so = range.startOffset, ec = range.endContainer, eo = range.endOffset;
    var startEndSame = (sc === ec);

    // Split the end boundary if necessary
    if (isCharacterDataNode(ec) && eo > 0 && eo < ec.length) {
        splitDataNode(ec, eo);
    }

    // Split the start boundary if necessary
    if (isCharacterDataNode(sc) && so > 0 && so < sc.length) {
        sc = splitDataNode(sc, so);
        if (startEndSame) {
            eo -= so;
            ec = sc;
        } else if (ec == sc.parentNode && eo >= getNodeIndex(sc)) {
            ++eo;
        }
        so = 0;
    }
    range.setStart(sc, so);
    range.setEnd(ec, eo);
}

function getTextNodesInRange(range) {
    var textNodes = [];
    var nodes = getNodesInRange(range);
    for (var i = 0, node, el; node = nodes[i++]; ) {
        if (node.nodeType == 3) {
            textNodes.push(node);
        }
    }
    return textNodes;
}

function surroundRangeContents(range, templateElement) {
    splitRangeBoundaries(range);
    var textNodes = getTextNodesInRange(range);
    if (textNodes.length == 0) {
        return;
    }
    for (var i = 0, node, el; node = textNodes[i++]; ) {
        if (node.nodeType == 3) {
            el = templateElement.cloneNode(false);
            node.parentNode.insertBefore(el, node);
            el.appendChild(node);
        }
    }
    range.setStart(textNodes[0], 0);
    var lastTextNode = textNodes[textNodes.length - 1];
    range.setEnd(lastTextNode, lastTextNode.length);
}

document.onmouseup = function() {
    if (window.getSelection) {
        var templateElement = document.createElement("span");
        templateElement.className = "highlight";
        var sel = window.getSelection();
        var ranges = [];
        var range;
        for (var i = 0, len = sel.rangeCount; i < len; ++i) {
            ranges.push( sel.getRangeAt(i) );
        }
        sel.removeAllRanges();

        // Surround ranges in reverse document order to prevent surrounding subsequent ranges messing with already-surrounded ones
        i = ranges.length;
        while (i--) {
            range = ranges[i];
            surroundRangeContents(range, templateElement);
            sel.addRange(range);
        }
    }
};
.highlight {
  font-weight: bold;
  color: red;
}
Select some of this text and it will be highlighted:

<ul>
    <li>the <b>only entry</b> of the list</li>
</ul>
<p>Some text here in paragraph</p>

<ul>
    <li>the <b>only entry</b> of the list</li>
</ul>
<p>Some text here in paragraph</p>

<ul>
    <li>the <b>only entry</b> of the list</li>
</ul>
<p>Some text here in paragraph</p>

【讨论】:

  • 这超出了我的预期! :)
  • 我真的认为我会采用这个解决方案。我还看了你的 Rangy 库,它真的令人印象深刻,所以向你推荐,谢谢!
  • 我非常感谢这个例子,感谢您提供如此完整的答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-06-23
  • 2010-10-19
相关资源
最近更新 更多