PR

JavaScriptのonfocus|使い方と動かない原因

JavaScript onfocusの確認事項を整理するイメージ 家電・IT
スポンサーリンク

JavaScriptのonfocusは、入力欄などがフォーカスを得たときの処理を登録するために使います。クリック時の処理と同じものではなく、Tabキーで入力欄へ移動した場合も確認できるイベントです。入力欄を選んだのに表示が変わらないときは、まず登録先と、実際にフォーカスを得ているかを切り分けましょう。

特につまずきやすいのが、親のフォームに通常のfocusリスナーを付けた場合です。focusはバブルしないため、子の入力欄で発生したイベントを親で扱うにはキャプチャを使うか、バブルするfocusinを選びます。要素に直接登録するonfocusやaddEventListenerとは、監視する位置が違います。

以下では文字が変わる最小例から、二つの登録方法、親で受け取る方法、動かないときの確認順を整理します。処理を増やす前に小さなHTMLで原因を確かめ、キーボード操作でも入力を妨げないコードへ直せるようにします。

スポンサーリンク

onfocusはフォーカスを得た時の処理を登録

MDNのfocusイベント解説では、要素がフォーカスを得たときにfocusが発生すると説明されています。入力欄なら、文字を入力する対象として選択された状態を考えると理解しやすいでしょう。反対に、フォーカスを失うときのイベントはblurです。

「入力欄をクリックしたら動く」という見方だけでは、キーボードからの操作を見落とします。Tabキーで対象へ移動しても確認できる例を使い、マウス専用のclick処理と分けて考えましょう。フォーカスの枠をCSSで消す必要はありません。

focusはキャンセルできないイベントでもあります。イベントを受け取った後にpreventDefaultでフォーカス取得そのものを取り消す、という設計にはしません。まずは状態を表示するなど、入力の邪魔をしない処理から試すと発生条件を把握しやすくなります。

onfocusとaddEventListenerの最小例

次のHTMLを使います。処理を登録するスクリプトは、このHTMLより後に置いてください。ラベルと入力欄、移動先のボタン、状態を表示する段落を用意し、入力欄を選択したかどうかが文字で分かる構成です。

<form id="demo">
  <label>名前 <input id="name" type="text"></label>
  <button type="button">次の要素</button>
</form>
<p id="status" role="status">未選択</p>

onfocusプロパティに関数を代入する

一つ目は入力要素のonfocusへ関数を代入する方法です。同じようにonblurへも登録し、入力欄からボタンへ移動したときの違いを見ます。外部へデータを送信する処理はありません。

<script>
const input = document.querySelector('#name');
const status = document.querySelector('#status');
input.onfocus = () => { status.textContent = '入力欄を選択しました'; };
input.onblur = () => { status.textContent = '入力欄から移動しました'; };
</script>

HTMLインライン属性のonfocusという書き方を見かける場合もありますが、ここではJavaScript側で要素を取得してから関数を登録します。HTMLの属性と、要素オブジェクトのプロパティを同じ記法だと思わず、どこにコードを書いているかを確認してください。

addEventListenerで登録する

もう一つはaddEventListenerにイベント名を渡す方法です。先ほどのスクリプトを、次のスクリプトへ置き換えて試します。比較用の例なので、二つを同時に貼り付けるのではなく、どちらか一方を使ってください。

<script>
const input = document.querySelector('#name');
const status = document.querySelector('#status');
input.addEventListener('focus', () => { status.textContent = '入力欄を選択しました'; });
input.addEventListener('blur', () => { status.textContent = '入力欄から移動しました'; });
</script>

イベント名はfocusです。onfocusという名前はプロパティ側の名前なので、addEventListenerへ渡す文字列と区別します。状態表示が変わらなければ、処理を複雑にする前に、入力要素と表示先を正しく取得できているか確認しましょう。

親要素のfocusが動かないのはバブルしないため

子の入力欄から親のフォームへイベントが広がることを期待して、親にfocusリスナーを登録しても、通常のバブルによる受け取りはできません。focusがバブルしないことが理由です。入力欄へ直接登録した例と同じように見えても、登録先の違いが結果に関わります。

親で監視する一つの方法がキャプチャです。次の例では第3引数のtrueで指定します。先ほどのHTMLを使い、スクリプトだけをこの例へ置き換えます。event.targetはイベントが発生した要素で、名前欄が対象のときだけ表示を変えています。

<script>
const form = document.querySelector('#demo');
const status = document.querySelector('#status');
form.addEventListener('focus', (event) => {
  if (event.target.id === 'name') status.textContent = '入力欄を選択しました';
}, true);
</script>

別の方法として、バブルするfocusinを親に登録できます。以下も独立した代替例です。キャプチャ例と同時に使って二重に処理する構成にはしていません。

<script>
const form = document.querySelector('#demo');
const status = document.querySelector('#status');
form.addEventListener('focusin', (event) => {
  if (event.target.id === 'name') status.textContent = '入力欄を選択しました';
});
</script>

MDNのfocusin解説も、focusinはバブルし、focusはバブルしないという違いを示しています。目的に合わせて監視方法を一つ選び、対象ブラウザでも確認してください。

登録方法 登録先 用途
focusを直接登録 対象入力欄 一つの入力欄の処理
focusをキャプチャ 親のフォームなど 親から子の取得を監視
focusinを登録 親のフォームなど バブルで受け取る監視

動かない時は登録先と発火条件を切り分け

最初に、querySelectorへ渡したIDとHTMLのIDを照合します。名前が異なれば意図した要素を取得できません。次に、要素が作られる前にスクリプトが動いていないかを確認します。この最小例ではスクリプトをHTMLの後ろへ置くことで、登録時点に対象要素がある構成にしています。

登録できているのに期待どおり動かない場合は、どの要素がフォーカスを持つか、直接の登録か親での監視か、イベント名はfocusかfocusinかを分けて見ます。複数の登録方法を一度に追加するより、表示を一行変える処理に戻して、どこまで動くかを確認する方が原因を追いやすくなります。

すでに選択している入力欄を繰り返し操作するだけでなく、別の要素へ移動してから戻る操作も試してください。フォーカスを新しく得る場面と、同じ要素に留まる場面を区別すると、クリックの回数とイベントの発生を同じものとして数えずに済みます。

Tab操作を残して実行結果を確認

最小例では、入力欄へTabキーで移動して「入力欄を選択しました」、次のボタンへ移動して「入力欄から移動しました」と表示が変わるかを確認します。後者のblur表示は直接登録した二つの例に含まれます。親監視の二つの例はfocus取得の表示だけなので、同じ終了表示を期待しないでください。

入力欄に焦点があることを示す枠も残したまま確認します。表示を整えるために枠だけを消すと、キーボードで今どこを操作しているか分かりにくくなります。状態文字の更新とフォーカス表示を両方見て、入力を妨げていないか確かめましょう。

本体のフォームへ組み込む前に、独立した例で直接登録、キャプチャ、focusinの違いを確認します。動く最小構成を一つ選んでから必要な処理を戻していけば、登録先の問題と追加処理の問題を切り分けやすくなります。

コメント

タイトルとURLをコピーしました