如何在内容可编辑元素(div)中设置脱字符号(光标)位置?

我有这个简单的HTML作为例子:

<div id="editable" contenteditable="true">
  text text text<br>
  text text text<br>
  text text text<br>
</div>
<button id="button">focus</button>

我想要简单的东西 - 当我点击按钮时,我想把脱字符号(光标)放在可编辑div中的特定位置。通过网络搜索,我将此JS附加到按钮单击,但它不起作用(FF,Chrome):

const range = document.createRange();
const myDiv = document.getElementById("editable");
range.setStart(myDiv, 5);
range.setEnd(myDiv, 5);

是否可以像这样手动设置插入记号位置?


答案 1

在大多数浏览器中,您需要“范围”和“选择”对象。将每个选区边界指定为一个节点,并在该节点内指定一个偏移量。例如,要将插入记号设置为第二行文本的第五个字符,请执行以下操作:

function setCaret() {
    var el = document.getElementById("editable")
    var range = document.createRange()
    var sel = window.getSelection()
    
    range.setStart(el.childNodes[2], 5)
    range.collapse(true)
    
    sel.removeAllRanges()
    sel.addRange(range)
}
<div id="editable" contenteditable="true">
  text text text<br>text text text<br>text text text<br>
</div>

<button id="button" onclick="setCaret()">focus</button>

IE < 9 的工作方式完全不同。如果您需要支持这些浏览器,则需要不同的代码。

jsFiddle 示例:http://jsfiddle.net/timdown/vXnCM/


答案 2

您在内容可编辑的光标定位上找到的大多数答案都相当简单,因为它们只适用于具有纯香草文本的输入。一旦在容器中使用html元素,输入的文本就会被拆分成节点,并在树结构中自由分布。

为了设置光标位置,我有这个函数,它循环所有子文本节点在提供的节点内,并设置从初始节点的开始到chars.count字符的范围:

function createRange(node, chars, range) {
    if (!range) {
        range = document.createRange()
        range.selectNode(node);
        range.setStart(node, 0);
    }

    if (chars.count === 0) {
        range.setEnd(node, chars.count);
    } else if (node && chars.count >0) {
        if (node.nodeType === Node.TEXT_NODE) {
            if (node.textContent.length < chars.count) {
                chars.count -= node.textContent.length;
            } else {
                range.setEnd(node, chars.count);
                chars.count = 0;
            }
        } else {
           for (var lp = 0; lp < node.childNodes.length; lp++) {
                range = createRange(node.childNodes[lp], chars, range);

                if (chars.count === 0) {
                    break;
                }
            }
        }
    } 

    return range;
};

然后,我使用以下函数调用例程:

function setCurrentCursorPosition(chars) {
    if (chars >= 0) {
        var selection = window.getSelection();

        range = createRange(document.getElementById("test").parentNode, { count: chars });

        if (range) {
            range.collapse(false);
            selection.removeAllRanges();
            selection.addRange(range);
        }
    }
};

range.collapse(false) 将光标设置为范围的末尾。我已经用最新版本的Chrome,IE,Mozilla和Opera测试了它,它们都可以正常工作。

如果有人感兴趣,我使用以下代码获取当前光标位置:

function isChildOf(node, parentId) {
    while (node !== null) {
        if (node.id === parentId) {
            return true;
        }
        node = node.parentNode;
    }

    return false;
};

function getCurrentCursorPosition(parentId) {
    var selection = window.getSelection(),
        charCount = -1,
        node;

    if (selection.focusNode) {
        if (isChildOf(selection.focusNode, parentId)) {
            node = selection.focusNode; 
            charCount = selection.focusOffset;

            while (node) {
                if (node.id === parentId) {
                    break;
                }

                if (node.previousSibling) {
                    node = node.previousSibling;
                    charCount += node.textContent.length;
                } else {
                     node = node.parentNode;
                     if (node === null) {
                         break
                     }
                }
           }
      }
   }

    return charCount;
};

该代码执行与 set 函数相反的操作 - 它获取当前 window.getSelection().focusNode 和 focusOffset,并向后计数遇到的所有文本字符,直到它命中 id 为 containerId 的父节点。isChildOf 函数只是在运行之前检查所支持的节点是否实际上是所提供 parentId 的子节点。

代码应该可以直接工作而无需更改,但是我刚刚从我开发的jQuery插件中获取了它,因此已经破解了其中的几个 - 如果有什么不起作用,请告诉我!