在内容可编辑的div中设置光标位置

Gau*_*don 7 html javascript google-chrome caret contenteditable

摘要:

我试图实现这样的效果:当用户键入a ([在内容可编辑时div,第二个)]自动插入,并且插入符号位于它们之间,即(和之间).


小提琴

键入--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;
    }
}
Run Code Online (Sandbox Code Playgroud)

笔记:

  1. <div>将有子元素(主要是<br>s).
  2. 使用vanilla JS只需要Chrome支持

我的问题:

  1. 为什么上述功能不起作用?
  2. 如何才能工作?

我花了几个小时搜索这个并没有发现任何有用的东西.有些是关于设置孩子的开始或结束,div但对我来说,它可以是任何位置,任何地方.

更新:

感谢大家终于完成了开发!

Tim*_*own 11

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

  • keypress是唯一可以可靠地检测键入了哪个字符的关键事件.keyup并且keydown不会这样做.
  • 代码通过阻止keypress事件的默认操作来手动处理括号/括号的插入.
  • 使用DOM方法时,选择/插入符号很简单.
  • 这在IE <= 8中不起作用,IE <= 8具有不同的范围和选择API.如果您需要支持这些浏览器,我建议使用我自己的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);
Run Code Online (Sandbox Code Playgroud)


Chr*_*sen 5

为了实现摘要中所述的目标,请尝试更改当前光标位置的节点值。由于您的代码附加到一个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;
Run Code Online (Sandbox Code Playgroud)

小提琴

另外,我发现您正在用来innerHTML设置新值(在 中Element.prototype.setText)。这让我听起来很震惊! innerHTML完全核爆容器中之前的所有内容。由于光标绑定到特定元素,而这些元素刚刚被破坏,浏览器应该做什么?如果您非常关心光标随后的位置,请尽量避免使用此功能。

至于highlightText问题,很难说为什么会坏。你的小提琴没有显示它在任何地方使用,我需要查看它的用法来进一步诊断它。但是,我对可能出现的问题有一个想法:

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

getCaretPosition还有另一个问题。对于您的可编辑div,它执行以下操作:

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

正如您所注意到的,某些元素(例如<br />)会影响 的结果toString()。有些元素(例如<span></span>)则不然。为了正确计算,您需要针对某些元素类型微调它,而不是其他元素类型。这将会变得混乱且复杂。如果你想要一个可以输入的数字highlightText并让它按预期工作,那么你的电流getCaretPosition不太可能有帮助。

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