web-dev-qa-db-ja.com

キープレスイベントが発生した後にテキストカーソルの位置を取得する方法

構文ハイライターを書いています。蛍光ペンは、テキストを入力して矢印キーで移動している間、すぐに強調表示を更新する必要があります。

私が直面している問題は、 '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

それ 0以外にkeypressは明らかに古いキャレット位置です。押し続けると  もう少し長くすると、次のようなものが得られます。

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

取得したいのは、「キーアップ」または「入力」で取得するような新しいキャレット位置です。 'keyup'の起動が遅すぎますが(キーが押されている間、構文を強調表示したい)、 'input'は実際に入力がある場合にのみ起動されます(しかし  入力を生成しません)。

入力時だけでなく、キャレット位置が変更された後に発生するイベントはありますか?または、テキストカーソルの位置を計算する必要がありますか? (テキストが折り返されて押すと、これはかなり複雑になると思います  (下矢印キー)。

14

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>

この方法は、キー処理の問題に対処します。あなたの例では、spanの内部にdiv要素もあり、それによって返される位置の値を変更します

window.getSelection().getRangeAt(0).startOffset
13
ConnorsFan

「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+/ 認識されません。

そして、おそらく私が考えていなかったもっと多くの問題があるでしょう。これらすべての問題を処理することは可能ですが、実装は非常に複雑になります。したがって、ConnorsFanが提供する単純なsetTimeout(..., 0)ソリューションは、 キャレットの位置変更のイベント が見つかるまでは間違いなく推奨されます。

1