|||||||||||||||||||||

なんぶ電子

- 更新: 

JavaScript:エンターキーでフォーカス移動

JavaScript

ブラウザでのエンターキーでのフォーカス移動はやはり一般的ではないのでしょうか?

筆者がそのようなコードはないかネットを探してみたところJavaScriptでコードでnextElementSiblingを利用したものが見つかりました。

ただ、これだと連続してinput要素がなければ使えません。また、textAreaには対応できていなさそうです。他にはjQueryで実装したものも見つかりましたが、JavaScriptだけでできないかと考えて、クラスとES2015のMapオブジェクトを使ってコードを作成してみました。

コードの稼働はブログ修正日時時点のバージョンのGoogle Chromeでのみ確認しております。他のブラウザで挙動がおかしかったらすみません。

サンプル

サンプルは次のようになります。「お名前」の部分にフォーカスを当てた後は、エンターキーで各要素への移動ができます。「同伴者」の部分だけ「申し込み」ボタンの後にフォーカスがあたるように、順序を変えています。また「18:00~」のラジオボタンにはフォーカスが当たらないようにしています。

サンプル

時間

その他要望

仕組み

仕組みとしては次のようになっています。

  1. querySelectorAllで対象となる要素を取得します

    ここではclass="intputs"のものを取得しています。

  2. 取得した要素を順序付けします

    順序付けにはtabIndex属性を使います。これに入っている数値の大小で並べ替えを行います。また0以下の値が入っていた場合はフォーカスの対象にしません。

  3. 順序をMapクラスにセットします。

    Mapのキーに「移動前」のエレメント、値に「移動先」のエレメントを設定します

  4. 各要素にエンターキーイベントをセットします

    エンターキーが押されたとき、イベント発生元のオブジェクトをキーにして、Mapから値を取得すると次にフォーカスする要素が返ります。その要素からfocus()メソッドを呼びます。

先の例では「同伴者」のtabindexを99にすることで、「申し込み」ボタンの後にフォーカスがあたるようにしています。またtabindexに-1をつけた「18:00~」にはフォーカスは自動で当たりません。

GoogleChromeの環境ではtabindexを指定していない場合値は0に設定されるようです。同じtabindexだった場合は要素を取得するメソッドの順序に依存します。

そして、おそらくこれがエンターキーでのフォーカス移動が一般的にならない理由のひとつだと思いますが、テキストエリアに設定してしまうと、エンターキーでフォーカスが移ってしまい改行がうまくできません。

なのでイベント処理の途中テキストエリアだけは別処理をします。ENTERならフォーカス移動、ALT+ENTERなら改行をします。さらに、テキストエリアにフォーカスが当たる際に preventDefault()を使って、デフォルトのイベントが伝播しないようにしないと、残っているエンターキーイベントによって、テキストエリアに意図しない改行が入ってしまいます。

コーディング

先のサンプルのソースは次のようになっています。

sample.html

<div>
<p><label>お名前<input class="inputs" type="text" name="mainname"/></label></p>
<p><label>同伴者<input class="inputs" type="text" name="subname" tabindex="99"/></label></p>
<p><label>プラン<select class="inputs" name="plan">
<option value="A">Aプラン</option>
<option value="B">Bプラン</option>
</select></label></p>
<p>時間<br>
<label><input class="inputs"  type="radio" name="hour" value="15">15:00~</label><br>
<label><input class="inputs" type="radio" name="hour" value="18" tabindex="-1">18:00~</label>
</p>
<p><label>利用規約に同意します。<input class="inputs" type="checkbox"></label></p>
<button class="inputs">申し込み</button>
<p>
その他要望<br>
<textarea class="inputs" name="memo" rows="5" cols="30" tabindex="100">このテキストエリアはエンターキーでフォーカス移動、ALT+エンターキーで改行します。</textarea>
</p>
</div>

次にJavaScript部です。もし既にhtmlを作成済みなら、次のコードをコピーしてbodyタグが閉じる前あたりに貼り付けて、querySelectorAllの部分を修正すれば、そのまま使えます。

sample.html

<!-- sample.htmlの続き -->
<script>
class FocusOrder {
  
  constructor() {
    this.nextElements = new Map();//tabOrderを記憶するマップ
    this.init=false;
  }
  
  initOrder() {
    //初期化
    //DOMを読み込んだ後に実行してください。
    
    if (this.init) {
      //このコードをは複数回呼ばれるのを想定していません
      console.log('すでに初期化済みです');
      return;
    }
    
    this.init = true;
    
    //ターゲットとなる要素をここに指定してください。 
    let target = document.querySelectorAll(".inputs");
    
    //getElementsByClassName等で取得することも可能です
    //let target = document.getElementsByClassName("inputs");
    
    //tabIndexにマイナスを指定してあったら除外
    let wk1 = [];
    for (let i = 0; i < target.length; i++) {
      if ( 0 <= target[i].tabIndex) {
        wk1.push(target[i]);
      }
    }
    
    //tabIndex順にソート
    let wk2=wk1.sort((a,b) => {
      if (a.tabIndex == b.tabIndex) {
        return 0;
      } else if(a.tabIndex > b.tabIndex) {
        return 1;
      } else {
        return -1;
      }
    });
    
    //次にフォーカスするエレメントをMapにセット
    for (let i =0; i < wk2.length-1; i++) {
      this.nextElements.set(wk2[i],wk2[i+1]);
    }
    this.nextElements.set(wk2[wk2.length-1],wk2[0]);
    
    //各エレメントにイベントをセット
    const keyevent = event => {
      if (event.key === 'Enter') {
        event.preventDefault();//eneterキーイベントの他の挙動を止めます。
      	if (event.srcElement) {
        	if (event.srcElement.tagName.toLowerCase().startsWith('textarea')) {
              if (event.altKey==true) {
                //要素がテキストエリアでALTがついていたら現在の位置に改行(\n)を入れて抜ける
                const intPosition = event.srcElement.selectionStart;
                const wk = event.srcElement.value;
                event.srcElement.value=wk.substring(0,intPosition)+'\n'+wk.substring(intPosition);
                //キャレットの位置を修正
                event.srcElement.selectionStart=intPosition+1;
                event.srcElement.selectionEnd=intPosition+1;
                return;
              }
            }
        }
        let elTarget = event.target;
        for(let i = 0, max=this.nextElements.size; i < max; i++) {
          let elWk = this.nextElements.get(elTarget)
            if (elWk.disabled || elWk.getBoundingClientRect().width == 0) {
              //要素が使用不能かdisplay noneの際は次の要素を検索
              elTarget=elWk;
            } else {
              elWk.focus();
              break;
            }
          }
        }
    };
    
    for (let i =0; i < wk2.length; i++) {
      wk2[i].onkeydown=function(event){keyevent(event)};
    }
  }
};

//クラスのインスタンスを作成
//クラスの定義の後に記述する必要があります。
let f = new FocusOrder();
//初期化
f.initOrder();
<script>
...

tabindexについて

MDN:TabIndexによれば、tabindexの最大値は32767ということでした。

エンターキー用にtabindexを設定しましたが、tabindexなので当然タブキーでのフォーカス移動の順序も変更することになりますので注意が必要です。関連して、 MDNのページでは「支援技術に頼っている人がページコンテンツを移動したり操作したりすることが難しくなります」という理由で、tabindexに0より大きい値を設定しないで(-1以外使わないで)下さいとも書いてありました。技術支援が必要な方の利用を想定しているページではtabindexを、data-属性などで置き換えてください。

ちなみにtabindexのかわりにdata-属性を使ったコードはgithubで紹介していますので、よろしければご覧になってください。またコードではコンストラクタでセレクタ文字列を受け取り、そのまま初期化するようにしています。

筆者紹介


自分の写真
がーふぁ、とか、ふぃんてっく、とか世の中すっかりハイテクになってしまいました。プログラムのコーディングに触れることもある筆者ですが、自分の作業は硯と筆で文字をかいているみたいな古臭いものだと思っています。 今やこんな風にブログを書くことすらAIにとって代わられそうなほど技術は進んでいます。 生活やビジネスでPCを活用しようとするとき、そんな第一線の技術と比べてしまうとやる気が失せてしまいがちですが、おいしいお惣菜をネットで注文できる時代でも、手作りの味はすたれていません。 提示されたもの(アプリ)に自分を合わせるのでなく、自分の活動にあったアプリを作る。それがPC活用の基本なんじゃなかと思います。 そんな意見に同調していただける方向けにLinuxのDebianOSをはじめとした基本無料のアプリの使い方を紹介できたらなと考えています。

広告