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


78

假设我有这个HTML元素:

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

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

我希望能够确定#parent开始选择的字符数(以及#parent选择结束的字符数)。即使他选择了HTML标签,这也应该起作用。(并且我需要它在所有浏览器中都能正常工作)

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


>即使他选择了HTML标签,这也应该起作用<您的意思是什么?有人将如何选择HTML标签?请解释。
Satyajit

如果用户选择中的所有内容#parent,则他的选择将包括一些HTML标记(<a>和<p>)
Tom Lehman

Answers:


197

更新

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

这是一个函数,将获取指定元素内插入符号的字符偏移量;但是,这是一个幼稚的实现,几乎肯定会与换行符产生不一致,并且不会尝试处理通过CSS隐藏的文本(我怀疑IE会正确地忽略此类文本,而其他浏览器则不会)。正确处理所有这些东西将很棘手。现在我已经尝试过了我的瘦长库。

实时示例: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;
}

2
@TimDown:太好了,谢谢!对于我的特定示例(使用TinyMCE),我实际上找到了一种更简单的方法:tinyMCE.execCommand('mceInsertContent', false, newContent);,在这里找到。但是+1可以帮助您!
Travesty3

4
@RafaelDiaz:是的,HTML或CSS暗示其他任何换行符。这不是理想的解决方案。
蒂姆·唐

1
@TimDown有什么方法可以修改它以通过该元素html来获取位置?例如HTML字符串:“ <div class =” c1“>你好</ div>并且光标已在Hello中出售” el“,它将返回17
Fred Johnson

1
@TimDown我建议win.getSelection().rangeCount > 0在运行之前添加检查win.getSelection().getRangeAt(0),以防止出现stackoverflow.com/questions/22935320/…中所述的错误。我自己遇到了问题,rangeCount检查修复了该问题
Gregor

2
@KimchiMan:您不再了。element.document适用于IE 5和5.5。
Tim Down

24

我知道这已经一岁了,但是这篇文章是关于找到Caret职位的许多问题的最佳搜索结果,我发现这很有用。

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

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

蒂姆的第一条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);

  if(selected){ // *
    caretOffset = preCaretRange.toString().length - selected; // *
  } else { // *
    caretOffset = preCaretRange.toString().length; 
  } // *
}

1
如果我已经有了偏移值(我从服务器端JSON响应中获取),并且想以此为基础突出显示单词,有人知道如何突出显示单词吗?我在rangy库上找不到任何东西,可以在其中插入两个值(start字符偏移量和stop字符偏移量)并突出显示单词。请指教。
约翰

我知道这是4岁后,但我们可以突出一个字在这种情况下,我必须选择的范围
Varun的

selected在减去之前是否要检查原因?如果它是0,那么您不是caretOffset通过减去0来更改它,对吧?
Donnie D'Amato

1
@ DonnieD'Amato好点。我进行了一些测试,当没有选择时(永远都未定义或null或其他意外内容),总会被选择为== 0,因此您应该放心跳过该检查并始终减去选择项。
科迪·克鲁姆琳

1
@CodyCrumrine顺便说一句,减去null等于使用0(但不确定在所有Javascript引擎中是否正确)。:)
aleclarson

15

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


我必须在if条件检查中包装getTextLength和getNodeTextLength if (node != null)。另外,node.parentNodeif (node != parent)
RugerSR9

非常完美,非常感谢。document.activeElement由于某种原因而无法使用,因此我使用了正在使用的单个div。
Xertz

1

该解决方案通过计算返回到父容器的先前同级兄弟的文本内容的长度来工作。尽管它可以处理任何深度的嵌套标签,但它可能不会涵盖所有边缘情况,但是如果您有类似的需求,它是一个很好的简单起点。

  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)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.