获取范围相对于其父容器的开始和结束偏移量

IT技术 javascript textrange
2021-01-13 21:45:04

假设我有这个 HTML 元素:

<div id="parent">
 Hello everyone! <a>This is my home page</a>
 <p>Bye!</p>
</div>

并且用户用他的鼠标选择“家”。

我希望能够确定#parent他的选择开始有多少个字符(以及#parent他选择结束后有多少个字符)。即使他选择了一个 HTML 标签,这也应该有效。(我需要它在所有浏览器中工作)

range.startOffset 看起来很有希望,但它只是相对于范围的直接容器的偏移量,并且仅当容器是文本节点时才是字符偏移量。

4个回答

更新

正如评论中指出的那样,我的原始答案(如下)仅返回选择的结尾或插入符号位置。修改代码以返回开始和结束偏移相当容易;这是一个这样做的例子:

这是一个函数,它将获取指定元素中插入符号的字符偏移量;然而,这是一个天真的实现,几乎肯定会与换行符不一致,并且不会尝试处理通过 CSS 隐藏的文本(我怀疑 IE 会正确忽略此类文本,而其他浏览器不会)。正确处理所有这些东西会很棘手。我现在已经为我的Rangy图书馆尝试了它

现场示例:http : //jsfiddle.net/TjXEG/900/

function getCaretCharacterOffsetWithin(element) {
    var caretOffset = 0;
    var doc = element.ownerDocument || element.document;
    var win = doc.defaultView || doc.parentWindow;
    var sel;
    if (typeof win.getSelection != "undefined") {
        sel = win.getSelection();
        if (sel.rangeCount > 0) {
            var range = win.getSelection().getRangeAt(0);
            var preCaretRange = range.cloneRange();
            preCaretRange.selectNodeContents(element);
            preCaretRange.setEnd(range.endContainer, range.endOffset);
            caretOffset = preCaretRange.toString().length;
        }
    } else if ( (sel = doc.selection) && sel.type != "Control") {
        var textRange = sel.createRange();
        var preCaretTextRange = doc.body.createTextRange();
        preCaretTextRange.moveToElementText(element);
        preCaretTextRange.setEndPoint("EndToEnd", textRange);
        caretOffset = preCaretTextRange.text.length;
    }
    return caretOffset;
}
@TimDown:太好了,谢谢!对于我的具体示例(使用 TinyMCE),我实际上找到了一种更简单的方法:tinyMCE.execCommand('mceInsertContent', false, newContent);,我在这里找到但是+1寻求帮助!
2021-03-14 21:45:04
@TimDown 我建议win.getSelection().rangeCount > 0在运行之前添加一个检查win.getSelection().getRangeAt(0),以防止在stackoverflow.com/questions/22935320/... 中描述的错误我自己遇到了这个问题,rangeCount 检查修复了它
2021-03-23 21:45:04
@TimDown 有什么方法可以修改它以通过该元素 html 获取位置吗?例如 Html string: "<div class="c1">Hello</div> 并且光标在 Hello 中选择了 "el" 它将返回 17
2021-03-25 21:45:04
@RafaelDiaz:是的,还有 HTML 或 CSS 暗示的任何其他换行符。这不是一个理想的解决方案。
2021-04-03 21:45:04
@KimchiMan:你不再是了。element.document适用于 IE 5 和 5.5。
2021-04-06 21:45:04

我知道这是一年前的文章,但这篇文章是有关查找 Caret 位置的许多问题的最佳搜索结果,我发现这很有用。

在内容可编辑的 div 中将元素从一个位置拖放到另一个位置后,我试图使用上面 Tim 的优秀脚本来找到新的光标位置。它在FF和IE中完美运行,但在Chrome中,拖动动作突出显示了拖动开始和结束之间的所有内容,导致返回caretOffset的太大或太小(按所选区域的长度)。

我在第一个 if 语句中添加了几行以检查是否选择了文本并相应地调整结果。新声明如下。如果在此处添加此内容不合适,请原谅我,因为这不是 OP 想要做的事情,但正如我所说,对与 Caret 位置相关的信息进行的几次搜索使我找到了这篇文章,因此(希望)可能会帮助其他人.

Tim 的第一个 if 语句添加了行 (*):

if (typeof window.getSelection != "undefined") {
  var range = window.getSelection().getRangeAt(0);
  var selected = range.toString().length; // *
  var preCaretRange = range.cloneRange();
  preCaretRange.selectNodeContents(element);
  preCaretRange.setEnd(range.endContainer, range.endOffset);

  caretOffset = preCaretRange.toString().length - selected; // *
}
@CodyCrumrine 顺便说一句,减去某些东西null相当于使用0(但不确定这是否在所有 Javascript 引擎中都正确)。:)
2021-03-17 21:45:04
selected在减去它之前有没有检查的原因如果它是 0,那么你不会caretOffset通过减去 0 来改变,对吗?
2021-03-24 21:45:04
我知道这是 4 年前的帖子,但是我们可以在这种情况下突出显示一个词吗,我可以选择范围
2021-03-28 21:45:04
如果我已经有了偏移值(我是从服务器端 JSON 响应中获取的)并且想要基于此突出显示单词,那么有谁知道如何突出显示一个单词?我在 rangy 库上找不到任何东西,我可以简单地插入这两个值(start字符偏移量和stop字符偏移量)并突出显示单词。请指教。
2021-03-29 21:45:04
@DonnieD'Amato 好点。我做了一些测试,并且在没有选择时总是被选中 == 0(从来没有未定义或空或任何其他意外),所以你应该安全地跳过该检查并始终减去所选内容。
2021-04-04 21:45:04

经过几天的试验,我发现了一种看起来很有希望的方法。因为selectNodeContents()不处理<br>正确的标签,我写了一个自定义的算法来确定每个文本长度nodecontenteditable为了计算例如选择开始,我总结了所有前面节点的文本长度。这样,我可以处理(多个)换行符:

非常感谢。document.activeElement由于某种原因没有工作,所以我使用了我正在使用的单个 div。
2021-03-15 21:45:04
我不得不将 getTextLength 和 getNodeTextLength 包装在 if 条件检查中if (node != null)另外我node.parentNode在之后添加了一个空值检查if (node != parent)
2021-03-27 21:45:04

此解决方案的工作原理是计算前一个兄弟姐妹的文本内容的长度,然后返回到父容器。它可能没有涵盖所有边缘情况,尽管它确实处理任何深度的嵌套标签,但如果您有类似的需求,它是一个很好的、简单的起点。

  calculateTotalOffset(node, offset) {
    let total = offset
    let curNode = node

    while (curNode.id != 'parent') {
      if(curNode.previousSibling) {
        total += curNode.previousSibling.textContent.length

        curNode = curNode.previousSibling
      } else {
        curNode = curNode.parentElement
      }
    }

   return total
 }

 // after selection

let start = calculateTotalOffset(range.startContainer, range.startOffset)
let end = calculateTotalOffset(range.endContainer, range.endOffset)