按键事件发生后如何获取文本光标位置?

How to get text cursor position after keypress event happened?

我正在写一个语法荧光笔。荧光笔应在输入文本和使用箭头键导航时立即更新突出显示。

我面临的问题是,当触发 'keypress' 事件时,您仍然可以通过 window.getSelection().

获得文本光标的旧位置

示例:

function handleKeyEvent(evt) {
  console.log(evt.type, window.getSelection().getRangeAt(0).startOffset);
}

var div = document.querySelector("div");
div.addEventListener("keydown", handleKeyEvent);
div.addEventListener("keypress", handleKeyEvent);
div.addEventListener("input", handleKeyEvent);
div.addEventListener("keyup", handleKeyEvent);
<div contenteditable="true">f<span class="highlight">oo</span></div>

在示例中,将插入符号放在单词 'foo' 之前,然后按 (右箭头键)。

在您最喜欢的 DevTool 的控制台中,您会看到以下内容:

keydown 0
keypress 0
keyup 1

除了 keypress 之外的 0 显然是旧的插入符号位置。如果你长按,你会得到这样的结果:

keydown 0
keypress 0
keydown 1
keypress 1
keydown 1
keypress 1
keydown 2
keypress 2
keyup 2

我想要得到的是新的插入符号位置,就像我为 'keyup' 或 'input' 得到的一样。虽然 'keyup' 触发得太晚(我想在按下键时突出显示语法)并且 'input' 仅在实际有一些输入时触发(但 不产生任何输入)。

是否有一个事件在插入符号位置改变后触发,而不仅仅是在输入时触发?或者我是否必须计算文本光标的位置,如果是,如何计算? (我假设当文本换行并按下 (向下箭头键)时,这会变得非常复杂。)

您可以使用 setTimeout 异步处理 keydown 事件:

function handleKeyEvent(evt) {
    setTimeout(function () {
        console.log(evt.type, window.getSelection().getRangeAt(0).startOffset);
    }, 0);
}

var div = document.querySelector("div");
div.addEventListener("keydown", handleKeyEvent);
<div contenteditable="true">This is some text</div>

该方法解决了密钥处理问题。在您的示例中,div 中还有一个 span 元素,它改变了

返回的位置值
window.getSelection().getRangeAt(0).startOffset

这是使用 'keydown' 事件更正位置的解决方案:

function handleKeyEvent(evt) {
  var caretPos = window.getSelection().getRangeAt(0).startOffset;

  if (evt.type === "keydown") {
    switch(evt.key) {
      case "ArrowRight":
        if (caretPos < evt.target.innerText.length - 1) {
          caretPos++;
        }
        break;

      case "ArrowLeft":
        if (caretPos > 0) {
          caretPos--;
        }
        break;

      case "ArrowUp":
      case "Home":
        caretPos = 0;
        break;

      case "ArrowDown":
      case "End":
        caretPos = evt.target.innerText.length;
        break;

      default:
        return;
    }
  }
  console.log(caretPos);
}

var div = document.querySelector("div");
div.addEventListener("keydown", handleKeyEvent);
div.addEventListener("input", handleKeyEvent);
<div contenteditable="true">f<span class="highlight">oo</span></div>

不幸的是,这个解决方案存在几个缺陷:

  • 当在示例中的 <span> 这样的子元素中时,它既不提供正确的 startOffset 也不提供正确的 startContainer.
  • 无法处理多行。
  • 它不处理所有导航选项。例如。通过 Ctrl+/ 逐字跳转无法识别。

而且可能还有更多我没有想到的问题。虽然可以处理所有这些问题,但它使实施变得非常复杂。因此,在 event for caret position changes.

之前,ConnorsFan 提供的简单 setTimeout(..., 0) 解决方案绝对更可取

有人刚刚在我的 request for an event for caret position changes that there is also a selectionchange event 中提到过,每次选择更改时都会触发文档。

这样就可以通过调用 window.getSelection().

来获得正确的光标位置

示例:

function handleSelectionChange(evt) {
    console.log(evt.type, window.getSelection().getRangeAt(0));
}

document.addEventListener("selectionchange", handleSelectionChange);
<div contenteditable="true">f<span class="highlight">oo</span></div>