3
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[JavaScript]テキストエリア間を矢印キーで移動したい

Last updated at Posted at 2020-07-05

やりたいこと

  • テキストエリア間のフォーカスをKeyboardEventイベントで発火させて移動させたい
  • フォーカスをテキストエリアの最初とか途中とか任意の場所に移動させたい
    • テキスト最後尾で↓キーを押したら次のテキストの先頭に移動させる
    • テキストの先頭で↑キーを押したら前のテキストの最後尾に移動させる

結論

  • keydownイベントで現在のテキストカーソルの位置を判定する
  • keyupイベントでフォーカスの移動、フォーカスの位置の指定を行う

See the Pen moving_focus by hodaka (@cornercrap) on CodePen.

動機

フォームなどのフォーカスの移動にはtabキーを使うことが一般的かと思いますが、矢印キーで操作できた方が操作性も上がるかなと思い検証しました。しかし、思ったより難航したのでここに書き留めておきます。

主な登場人物

keydown/keyupイベント

それぞれキーを押したとき/離したときに発生するイベント

KeyboardEvent.code

押された/離したキーの種類

focus()

その要素がフォーカスされる

setSelectionRange(selectionStart, selectionEnd)

任意のテキストの開始と終了のインデックスを指定することでテキストを選択状態にする
→引数に同じインデックスを指定すればテキストカーソルの移動と同じ意味になる

今回操作するHTMLはこんな感じです。

HTML
<div>
  <textarea id="textarea">テキストテキスト</textarea>
</div>
<div>
  <textarea id="textarea2">テキスト2テキスト2</textarea>
</div>

失敗例1

まず手始めにkeyupだけでできるだろうと思い、組んでみました。

失敗例1
const textArea = document.getElementById("textarea");
const textArea2 = document.getElementById("textarea2");

textArea.addEventListener("keyup", event => { // use keyup event
  if(event.code == "ArrowDown" && textArea.selectionStart == textArea.value.length){
      textArea2.focus();
  }
});
textArea2.addEventListener("keyup", event => {
  if(event.code == "ArrowUp" && textArea2.selectionStart == 0){
      textArea.focus();
  }
});

カーソルがテキストの途中にあってもフォーカスが移動してしまいます。
↓キーを押すとテキストカーソルは、複数行ある場合は次の行へ移動する、最終行だった場合、最後尾に移動します。
最後尾に移動してしまうと後半の条件式が満たされてしまうのでフォーカスが移動してしまいます。

失敗例2

ならばとkeydownで試してみます。テキストカーソルの位置が想定どおりに行かないのでsetSelectionRange()で明示的に指定しています。

失敗例2
const textArea = document.getElementById("textarea");
const textArea2 = document.getElementById("textarea2");

textArea.addEventListener("keydown", event => { // use keydown event
  if(event.code == "ArrowDown" && textArea.selectionStart == textArea.value.length){
      textArea2.focus();
      textArea2.setSelectionRange(0, 0);
  }
});
textArea2.addEventListener("keydown", event => {
  if(event.code == "ArrowUp" && textArea2.selectionStart == 0){
      textArea.focus();
      textArea.setSelectionRange(textArea.value.length, textArea.value.length);
  }
});

setSelectionRange()を使ってもカーソルの位置が指定できなくなってしまいました。

成功例?

keydownの中ではsetSelectionRange()がうまく機能していませんでした。
しかし、調べていたところ、setTimeout()を使って回避する方法が見つかりました。こちらを参考にしてみます。

成功例?
const textArea = document.getElementById("textarea");
const textArea2 = document.getElementById("textarea2");

textArea.addEventListener("keydown", (event) => {
  if (
    event.code == "ArrowDown" &&
    textArea.selectionStart == textArea.value.length
  ) {
    window.setTimeout(() => { // use setTimeout()
      textArea2.focus();
      textArea2.setSelectionRange(0, 0);
    });
  }
});
textArea2.addEventListener("keydown", (event) => {
  if (event.code == "ArrowUp" && textArea2.selectionStart == 0) {
    window.setTimeout(() => {
      textArea.focus();
      textArea.setSelectionRange(textArea.value.length, textArea.value.length);
    });
  }
});

不思議なことに想定どおりに動いてくれています。とりあえず実装したい場合はこれでいいと思いますが、この解法に関しては最善策でないと述べられていますし、私自身動く理由がよくわからないので、もう少し模索してみます。

成功例(結論)

keyupではカーソルの位置が正しく判定されず、keydownではフォーカス後のカーソルがうまく指定できませんでした。
なので両方のイベントを使ってそれぞれの欠点を補います。

成功例(結論)
let movingFlg = false;

textArea.addEventListener("keydown", (event) => {
  if (
    event.code == "ArrowDown" &&
    textArea.selectionStart == textArea.value.length
  ) {
    movingFlg = true;
  }
});
textArea.addEventListener("keyup", (event) => {
  if (event.code == "ArrowDown" && movingFlg) {
    movingFlg = false;
    textArea2.focus();
    textArea2.setSelectionRange(0, 0);
  }
});

textArea2.addEventListener("keydown", (event) => {
  if (event.code == "ArrowUp" && textArea2.selectionStart == 0) {
    movingFlg = true;
  }
});
textArea2.addEventListener("keyup", (event) => {
  if (event.code == "ArrowUp" && movingFlg) {
    movingFlg = false;
    textArea.focus();
    textArea.setSelectionRange(textArea.value.length, textArea.value.length);
  }
});

keydownでは単に移動するべきかどうかを判定して、フォーカスやカーソル位置指定はkeyupで行います。フラグも出現してコード量も多くなりましたが腑に落ちるレベルまでは到達できました。

終わりに

改善案・ご指摘・捕捉など歓迎です。優しく教えてください。

3
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?