首页 > 解决方案 > 如何获取 ContentEditable 元素中相对于 innerText 的插入符号位置?

问题描述

我知道有很多关于在ContentEditable元素中获取插入符号位置的现有问题。几乎所有这些现有的解决方案都提供了相对于textContent. 一些例子是this onethis one

我们目前正在开发两个 WebExtensions 来根据用户类型进行自动更正。例如,如果用户键入:),它可以将其自动更正为 。为了使自动更正起作用,它需要获得相对于innerText. textContent可以包含空白字符和其他在呈现文本时实际上不会出现的差异,这会破坏自动更正功能。

我们当前的方法部分改编自这个答案:https ://stackoverflow.com/a/29258657 ,它提供了相对于innerHTML. 它克隆元素,插入空字符,确定索引,然后删除空字符:

// document.designMode is handled the same, see  https://github.com/rugk/unicodify/issues/54
if (target.isContentEditable || document.designMode === "on") { 
     target.focus(); 
     const _range = document.getSelection().getRangeAt(0); 
     if (!_range.collapsed) { 
         return null; 
     } 
     const range = _range.cloneRange(); 
     const temp = document.createTextNode("\0"); 
     range.insertNode(temp); 
     const caretposition = target.innerText.indexOf("\0"); 
     temp.parentNode.removeChild(temp); 
     return caretposition; 
}

有关完整上下文,请参阅我们的原始源代码。这种方法似乎在 99% 的网站上都可以正常工作,但在Twitter上就失效了。当用户键入时,光标会不断地重置到行首,这会打乱文本(有关更多信息,请参阅相应的问题)。我们猜测 Twitter 不喜欢 null 字符,但我们尝试使用其他非打印字符并遇到同样的问题。

我们正在寻找另一种确定插入符号位置的方法,该方法innerText适用于所有网站,包括 Twitter。它需要支持最新版本的 Firefox 和 Chrome,包括 Firefox ESR。它还需要高性能,因为它在每次按键时都会运行。


交叉发布在Mozilla 的 Discourse上。

标签: javascriptgoogle-chrome-extensioncontenteditablecaretcursor-position

解决方案


脚步


我从元素中得到SelectionandRangetarget

const selection = document.getSelection();
const range = document.createRange();

检查是否Range折叠后,插入null字符并获取插入符号位置的基本代码

const temp = document.createTextNode("\0");
selection.getRangeAt(0).insertNode(temp);
caretPosition = target.innerText.indexOf("\0");
temp.parentNode.removeChild(temp);

然后我使用这个片段来设置插入符号的位置,这应该是 Twitter 的修复

range.setStart(selection.focusNode, selection.focusOffset);
range.collapse(false);

selection.removeAllRanges();
selection.addRange(range);

const target = document.getElementById('text');

target.addEventListener('keyup', () => {
  let caretPosition = null;
  const selection = document.getSelection();
  const range = document.createRange();
  
  if (range.collapsed) {
    const temp = document.createTextNode("\0");
    selection.getRangeAt(0).insertNode(temp);
    caretPosition = target.innerText.indexOf("\0");
    temp.parentNode.removeChild(temp);

    range.setStart(selection.focusNode, selection.focusOffset);
    range.collapse(false);

    selection.removeAllRanges();
    selection.addRange(range);
  }
  
  console.log(JSON.stringify({ caretPosition }));
});
#text {
  border: 1px solid;
  padding: 1rem;
  width: 50vw;
  height: 50vh;
}
<h3>ContentEditable Div</h3>
<div id="text" contenteditable="true">This text can be edited by the user.<br> Some <strong>bold</strong> and <em>italic and <strong>bold</strong></em> text.</div>


推荐阅读