如何使用多个文本节点可靠地获取Javascript中单击的字符偏移量?

如何使用多个文本节点可靠地获取Javascript中单击的字符偏移量?,javascript,html,browser,web-frontend,textselection,Javascript,Html,Browser,Web Frontend,Textselection,我用它来获取用户单击某个文本时的字符偏移量。当只有一个文本节点时,这种方法非常有效,关于如何实现这一点,有很多很好的答案 但是,当两个或多个文本节点相邻渲染时,我遇到了一个问题,但我将在这里介绍: 用例: <p class="sentence"> <span class="word" index="0">There </span><span class="word" in

我用它来获取用户单击某个文本时的字符偏移量。当只有一个文本节点时,这种方法非常有效,关于如何实现这一点,有很多很好的答案

但是,当两个或多个文本节点相邻渲染时,我遇到了一个问题,但我将在这里介绍:

用例

<p class="sentence">
  <span class="word" index="0">There </span><span class="word" index="1">is </span><span class="word" index="2">a </span><span class="word" index="3">big, </span><span class="word" index="4">wonderful </span><span class="word" index="5">world.</span>
</p>
$('.word').click(function() {
  let word_index = $(this).attr('index');
  
  let selection  = window.getSelection();
  let char_index = selection.focusOffset;

  console.log('Clicked to set a new cursor @');
  console.log('Word index = ' + word_index.toString() + 
             ' / char index = ' + char_index.toString() );
});
我正在构建一个文本编辑器,并使用Javascript控制DOM,Javascript对用户按键做出反应,而不是使用contentEditable容器。我希望跟踪(并显示)用户的“光标”在文本中的位置(即,如果他们开始键入文本,将在哪里输入文本),并让他们单击文本中的任何位置,手动将光标设置为单击的位置

HTML

<p class="sentence">
  <span class="word" index="0">There </span><span class="word" index="1">is </span><span class="word" index="2">a </span><span class="word" index="3">big, </span><span class="word" index="4">wonderful </span><span class="word" index="5">world.</span>
</p>
$('.word').click(function() {
  let word_index = $(this).attr('index');
  
  let selection  = window.getSelection();
  let char_index = selection.focusOffset;

  console.log('Clicked to set a new cursor @');
  console.log('Word index = ' + word_index.toString() + 
             ' / char index = ' + char_index.toString() );
});
简而言之,该代码打印一个
单词索引
和一个
字符索引
,用于光标在文本中的位置,就像它是一个可编辑的输入一样。如果你点击“There”中“T”的左半部分,它就会打印出来

Clicked to set a new cursor @
Word index = 0
/ char index = 0
单击“T”的右侧(这样光标将放在“h”之前的“T”后面),将打印相同的单词索引,但字符索引为1,因为光标现在放置在一个字符之上,依此类推

这也很有效。。。除了单击任何单词第一个字符的左半部分(第一个字符除外)。单击“is”(在单词索引1处设置插入符号,字符索引0)中“i”的左半部分,将打印单词索引1(正确)和字符索引6(前一个单词的长度)

当多个文本节点相邻时,
Window.getSelection
(或者更具体地说,
selection.focusOffset
)不是计算这种字符偏移量的正确方法吗?是否需要使用其他库或方法来代替

解决这个问题的一个“办法”是在每个单词周围加一个空白,在它们之间留出一个不可点击的间隙。在这种情况下,单击“i”的左半部分会给出正确的单词/字符索引(1,0而不是1,6),但会产生一个副作用,即如果用户意外单击此处,则会出现空白(这是一个严重的副作用,因此它不是一个真正可行的“修复”)。看起来至少3px的边距总是返回正确的值,而2px或更小的边距总是返回不正确的值

我正在用Chrome进行测试,因为我最终将构建成一个电子应用程序,所以我想我真的只需要它在Blink中工作,但如果一个解决方案对于web版本来说也是浏览器无关的,那就太好了


解决方案更新

<p class="sentence">
  <span class="word" index="0">There </span><span class="word" index="1">is </span><span class="word" index="2">a </span><span class="word" index="3">big, </span><span class="word" index="4">wonderful </span><span class="word" index="5">world.</span>
</p>
$('.word').click(function() {
  let word_index = $(this).attr('index');
  
  let selection  = window.getSelection();
  let char_index = selection.focusOffset;

  console.log('Clicked to set a new cursor @');
  console.log('Word index = ' + word_index.toString() + 
             ' / char index = ' + char_index.toString() );
});
我能够通过实施以下两个附加护栏来实现这一点:

// If we hit an overlap with another bit of selectable
// text, we zero out the cursor offset to avoid using
// that word's offset -- the left edge of a textnode
// is the only place where this happens, so we know
// the correct cursor offset is 0.
if ($(this).text() !== selection.focusNode.wholeText)   {
  char_index = 0;
}

// There's also a small clickable area on the right
// side of the final character in a word that would
// result in a cursor at the end of that word (often
// where the delimiting space is). We simply shift
// the cursor's reference from end-of-that-word to
// start-of-the-next word, which are the same to the
// user, but makes more sense for keeping proper
// word individuation.
if (char_index === $(this).text().length) {
  word_index += 1;
  char_index  = 0;
}

有更多的工作要跨浏览器进行,但需要使用这些调整来获得Chrome中所需的值。

默认行为取决于浏览器,因此您需要进行一些检查,以获得一致的结果

下面是一个简单的示例,它比较
事件.target
元素与
选择返回的文本节点的
父元素
。focusNode
,如果不匹配,它会检查焦点节点的同级是否匹配,并相应地调整偏移量

您需要使其更加健壮。您还需要处理方向性(从左到右选择会产生反转,从右到左选择节点)。你也可以看看

功能手柄选择(e){
const selection=window.getSelection();
const fNode=selection.focusNode;
const fElement=fNode.parentElement;
const-tElement=e.target;
让word_index=tElement.dataset.index;
让char_index=selection.focusOffset;
if(!fElement.isEqualNode(远程通讯)
&&FELENT.NEXTLEMENTSIBLING.isEqualNode(tElement)){
char_指数=0;
}
console.log(
“fNode:”,fNode.parentElement.innerText,
“tNode:”,tElement.innerText,
“char:”,char_索引,
“单词:”,单词索引
);
}
const words=document.queryselectoral('.word');
words.forEach((w,i)=>(
w、 dataset.index=i+1,
w、 addEventListener('click',handleSelection))
.word{
边框:1px纯色灰色;
字号:2rem;
}

你好,奇妙的世界。


默认行为取决于浏览器,因此您需要进行一些检查,以获得一致的结果

下面是一个简单的示例,它比较
事件.target
元素与
选择返回的文本节点的
父元素
。focusNode
,如果不匹配,它会检查焦点节点的同级是否匹配,并相应地调整偏移量

您需要使其更加健壮。您还需要处理方向性(从左到右选择会产生反转,从右到左选择节点)。你也可以看看

功能手柄选择(e){
const selection=window.getSelection();
const fNode=selection.focusNode;
const fElement=fNode.parentElement;
const-tElement=e.target;
让word_index=tElement.dataset.index;
让char_index=selection.focusOffset;
if(!fElement.isEqualNode(远程通讯)
&&FELENT.NEXTLEMENTSIBLING.isEqualNode(tElement)){
char_指数=0;
}
console.log(
“fNode:”,fNode.parentElement.innerText,
“tNode:”,tElement.innerText,
“char:”,char_索引,
“单词:”,单词索引
);
}
const words=document.queryselectoral('.word');
words.forEach((w,i)=>(
w、 dataset.index=i+1,
w、 addEventListener('click',handleSelection))
.word{
边框:1px纯色灰色;
字号:2rem;
}

你好,奇妙的世界。


现在没有时间看这个,也没有仔细阅读你的任务