【问题标题】:Set cursor position in content-editable div在内容可编辑的 div 中设置光标位置
【发布时间】:2014-06-20 14:07:31
【问题描述】:

总结

我正在尝试实现这样的效果:当用户在内容可编辑的div 中键入([ 时,第二个)] 会自动插入,并且插入符号被定位两者之间,即()之间。


FIDDLE

--s 的右侧键入,看看它在第一行中如何工作,而在第二行中不起作用。


我的努力:

我正在使用这段代码(Tim Down)来突出显示文本的某些部分并设置光标位置。前者有效,但后者无效:(

function getTextNodesIn(node) { // helper
    var textNodes = [];
    if (node.nodeType == 3) {
        textNodes.push(node);
    } else {
        var children = node.childNodes;
        for (var i = 0, len = children.length; i < len; ++i) {
            textNodes.push.apply(textNodes, getTextNodesIn(children[i]));
        }
    }
    return textNodes;
}

function highlightText(el, start, end) { // main
    if (el.tagName === "DIV") { // content-editable div
        var range = document.createRange();
        range.selectNodeContents(el);
        var textNodes = getTextNodesIn(el);
        var foundStart = false;
        var charCount = 0,
            endCharCount;

        for (var i = 0, textNode; textNode = textNodes[i++];) {
            endCharCount = charCount + textNode.length;
            if (!foundStart && start >= charCount && (start < endCharCount || (start == endCharCount && i < textNodes.length))) {
                range.setStart(textNode, start - charCount);
                foundStart = true;
            }
            if (foundStart && end <= endCharCount) {
                range.setEnd(textNode, end - charCount);
                break;
            }
            charCount = endCharCount;
        }

        var sel = window.getSelection();
        sel.removeAllRanges();
        sel.addRange(range);
    } else { // textarea
        el.selectionStart = start;
        el.selectionEnd = end;
    }
}

注意事项:

  1. &lt;div&gt; 会有子元素(主要是&lt;br&gt;s)。
  2. 使用 vanilla JS 仅需要 Chrome 支持

我的问题:

  1. 为什么上面的函数不起作用?
  2. 如何让它发挥作用?

我花了好几个小时寻找这个并没有发现任何有用的东西。有些是关于在孩子的开头或结尾设置div,但对我来说,它可以是任何位置、任何地方。

更新

感谢大家终于是finished development

【问题讨论】:

    标签: javascript html google-chrome contenteditable caret


    【解决方案1】:

    这是一个更简单的方法。有几点需要注意:

    • keypress 是唯一可以可靠地检测到输入了哪个字符的关键事件。 keyupkeydown 不行。
    • 代码通过阻止keypress 事件的默认操作来手动处理括号/大括号的插入。
    • 使用 DOM 方法时,选择/插入符号很简单。
    • 这在 IE Rangy 库。没有它是可能的,但我真的不想编写额外的代码。

    演示:

    http://jsfiddle.net/HPeb2/

    代码:

    var editableEl = document.getElementById("editable");
    editableEl.addEventListener("keypress", function(e) {
        var charTyped = String.fromCharCode(e.which);
        if (charTyped == "{" || charTyped == "(") {
            // Handle this case ourselves
            e.preventDefault();
    
            var sel = window.getSelection();
            if (sel.rangeCount > 0) {
                // First, delete the existing selection
                var range = sel.getRangeAt(0);
                range.deleteContents();
    
                // Insert a text node at the caret containing the braces/parens
                var text = (charTyped == "{") ? "{}" : "()";
                var textNode = document.createTextNode(text);
                range.insertNode(textNode);
    
                // Move the selection to the middle of the inserted text node
                range.setStart(textNode, 1);
                range.setEnd(textNode, 1);
                sel.removeAllRanges();
                sel.addRange(range);
            }
        }
    }, false);
    

    【讨论】:

    • 有趣的解决方案。我将在 2-3 天内对此进行试验。
    • 谢谢!这正是我所需要的!附言您有什么建议/答案来克服我拥有的getCaretPosition 功能的限制吗?
    【解决方案2】:

    要实现摘要中所述的目标,请尝试更改当前光标位置的节点值。由于您的代码已附加到 keyup 事件,因此您可以确保该范围已经折叠并位于文本节点上(所有这些都发生在按键按下时,这已经触发了)。

    function insertChar(char) {
        var range = window.getSelection().getRangeAt(0);
        if (range.startContainer.nodeType === Node.TEXT_NODE) {
            range.startContainer.insertData(range.startOffset, char);
        }
    }
    
    function handleKeyUp(e) {
        e = e || window.event;
    
        var char, keyCode;
        keyCode = e.keyCode;
    
        char =
            (e.shiftKey ? {
                "222": '"',
                "57":  ')',
                "219": '}'
            } : {
                "219": "]"
            })[keyCode] || null;
    
        if (char) {
            insertChar(char);
        }
    }
    
    document.getElementById("editable").onkeyup = handleKeyUp;
    

    Fiddle

    另外,我看到您使用innerHTML 设置新值(在Element.prototype.setText 中)。这听起来让我很震惊! innerHTML 完全破坏了容器中以前的任何内容。由于光标被绑定到一个特定的元素,而这些元素刚刚被破坏,浏览器应该做什么?如果您完全关心您的光标在哪里结束,请尽量避免使用它。

    至于highlightText 问题,很难说它为什么坏了。您的小提琴没有显示它在任何地方使用,我需要查看它的用法以进一步诊断它。但是,我知道可能出了什么问题:

    我认为你应该仔细看看getCaretPosition。您将其视为返回光标位置,但这不是它的作用。请记住,对于浏览器,您的光标位置始终 是一个范围。它总是有一个开始和一个结束。有时,当范围折叠时,开始和结束是同一点。但是,您可以获取光标位置并将其视为单个点的想法过于简单化是危险的。

    getCaretPosition 有另一个问题。对于您的可编辑div,它会这样做:

    1. 选择新范围内的所有文本
    2. 将新范围的结束位置重置为光标的结束位置(因此直到光标结束位置的所有文本都被选中)。
    3. 调用toString() 并返回结果字符串的长度。

    正如您所指出的,某些元素(例如&lt;br /&gt;)会影响toString() 的结果。有些元素(例如&lt;span&gt;&lt;/span&gt;)没有。为了使这个计算正确,您需要为某些元素类型而不是其他元素类型微调它。这将是混乱和复杂的。如果您想要一个可以输入到highlightText 并使其按预期工作的号码,那么您当前的getCaretPosition 不太可能有帮助。

    相反,我认为您应该尝试直接将光标的起点和终点作为两个单独的位置,并相应地更新highlightText。丢弃当前的getCaretPosition,直接使用浏览器原生的range.startContainerrange.startOffsetrange.endContainerrange.endOffset概念。

    【讨论】:

    • 这是一个非常有见地的答案!谢谢你指导我!我真的很想知道所有这些繁重方法的最佳解决方案就是range.startContainero.O 我想我需要在这件事上做很多阅读。再次感谢!!!
    • 但是你能解释一下if (range.startContainer.nodeType === Node.TEXT_NODE) { 的必要性吗? .insertData 是什么?抱歉,我找不到他们的文档。谢谢:)
    • 另外,我如何将插入符号放置在内容可编辑的 div 中的任何位置?(就在任何位置,与此大括号完成功能无关)我再次在 Google 中搜索了过去的两个页面,但没有结果。
    • 实际上&lt;br&gt; 元素对范围的toString() 方法返回的字符串没有贡献:仅包含来自文本节点的文本。但是,Selection.toString() 并非如此(在某些浏览器中)。从中获取getCaretPosition() 的原始答案非常清楚地列出了它的局限性。我绝对同意你所说的关于小心 DOM 操作的一切,这比使用 innerHTML 更容易。
    • @GaurangTandon:已经提到了用于做你想做的事情的相关 API(DOM RangeSelection):你得到的任何答案都将基于它们。 insertDataa method of DOM text nodes。顺便说一句,我得到了最后一个链接作为谷歌搜索“mdn insertdata”的第一个结果。
    猜你喜欢
    • 1970-01-01
    • 2020-05-10
    • 2011-11-05
    • 2011-01-13
    • 2016-07-05
    • 2023-01-10
    • 1970-01-01
    • 2014-02-14
    • 1970-01-01
    相关资源
    最近更新 更多