在包含HTML内容的contentEditable区域中获取插入符号(光标)位置

61

我有一个可编辑的元素(可以是p、div等),我想在其中获取插入符(光标)位置。我可以使用以下代码来实现:

var position = window.getSelection().getRangeAt(0).startOffset;

如果元素只包含文本,那么这段代码可以很好地工作。但是当元素包含一些HTML格式时,返回的位置将相对于所包含的HTML元素内的插入符位置。

假设contentEditable元素的内容如下:

AB<b>CD</b>EF

如果插入符号在 <b></b> 内部,比如在 C 和 D 之间,使用上述代码返回的位置将是 1,而不是从内容可编辑元素的开头开始计算的 3。

有人能想出解决方案吗?


如果您想在可编辑元素中获取字符偏移量,我可以问一下原因吗?可能有更好的方法来实现您想要的功能。 - Tim Down
3
我有自己的所见即所得编辑器,它故意与所有常见的编辑器有所不同。每个<p>都启用contentEditable。现在我正在尝试解决一个问题,当用户仅使用箭头键从一个段落移动到另一个段落时。因此,我需要检测光标在段落中的位置,以便根据按下的箭头键重新定位它。 - Frodik
3个回答

54

更新

我写了一个更简单的版本,它也适用于IE < 9:

https://dev59.com/iG445IYBdhLWcg3wia2V#4812022

旧答案

实际上,这比整个文档中的字符偏移更有用:DOM Range(即window.getSelection().getRangeAt()返回的内容)的startOffset属性是相对于其startContainer属性的偏移量(顺便说一下,它并不总是一个文本节点)。但是,如果你真的需要一个字符偏移量,这里有一个可以做到的函数。

这是一个实时例子:http://jsfiddle.net/timdown/2YcaX/

这是函数:

function getCharacterOffsetWithin(range, node) {
    var treeWalker = document.createTreeWalker(
        node,
        NodeFilter.SHOW_TEXT,
        function(node) {
            var nodeRange = document.createRange();
            nodeRange.selectNode(node);
            return nodeRange.compareBoundaryPoints(Range.END_TO_END, range) < 1 ?
                NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
        },
        false
    );

    var charCount = 0;
    while (treeWalker.nextNode()) {
        charCount += treeWalker.currentNode.length;
    }
    if (range.startContainer.nodeType == 3) {
        charCount += range.startOffset;
    }
    return charCount;
}

4
@Frodik:完成了。请查看http://jsfiddle.net/timdown/2YcaX/3/。我还更新了答案。 - Tim Down
1
@Frodik:既然我在其他地方没有提到过这一点,那我就在这里提一下:这个答案对于IE <= 8是不起作用的。我没有尝试为IE提供解决方案,因为你最初的问题是使用window.getSelection(),而这在IE中不被支持。 - Tim Down
首先,感谢您提供这个精彩的答案。但是我有一个问题。它没有计算换行符。如果我在末尾放置三个换行符,通过此函数获得的插入符位置仍然保持不变。您能帮助我解决这个问题吗? - varunvs
@varunvs:我为我的Rangy库编写了一个更复杂的版本:https://code.google.com/p/rangy/wiki/TextRangeModule - Tim Down
我只需要获取光标位置,以便在插入链接之前恢复它。Rangy 不是有点过度了吗? - Ced
显示剩余4条评论

17

这是一篇非常古老的帖子,但在谷歌搜索中仍然是第一条结果之一,因此可能仍然有用。这对我来说可行,可以考虑HTML标记和换行符的正确位置(已在Firefox上测试):

function getCaretPosition (node) {
    var range = window.getSelection().getRangeAt(0),
        preCaretRange = range.cloneRange(),
        caretPosition,
        tmp = document.createElement("div");

    preCaretRange.selectNodeContents(node);
    preCaretRange.setEnd(range.endContainer, range.endOffset);
    tmp.appendChild(preCaretRange.cloneContents());
    caretPosition = tmp.innerHTML.length;
    return caretPosition;
}
它使用cloneContents功能来获取实际的html,并将文档片段添加到临时div中以获取html长度。

1
这对我来说比目前被接受的答案(包括作者的原始和更新版本)更有效,适用于带有格式化HTML的contentEditable div。 - CFitz

1

如果你想插入元素,可以尝试像这样做:

// Get range
var range = document.caretRangeFromPoint(event.clientX, event.clientY);
if (range)
  range.insertNode(elementWhichYouWantToAddToContentEditable);

3
看起来 caretRangeFromPoint 是只有在 Firefox 中可用的,这很遗憾。 - Michael
1
使用webkit的document.caretRangeFromPoint(x,y)和firefox的document.caretPositionFromPoint(x,y)有什么区别?请参考https://developer.mozilla.org/en-US/docs/Web/API/Document/caretPositionFromPoint。不确定这个是否有效:https://gist.github.com/unicornist/ac997a15bc3211ba1235。 - Antti
此解决方案已于2022年停用。 - KYin

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接