⭐️ 動作確認用サンプル: https://jsfiddle.net/2ub63Lto/
処理の概要
- まずは、フォーカス可能な要素に座標番号のデータ属性をJSで動的に付与します。
- 例えば、
data-coord="0-0"
を付与
- →
<button data-coord="0-0"><span>ボタン1</span></button>
- JSにより自動で付与されますので、自分で考えながらHTMLに座標番号を書かなくてもいいです。
- 左上を起点として
(x=0, y=0)
とすると、各要素の座標は以下の画像の通りとなります。
- JavaScriptでフォーカス可能な要素を全て取得し、2次元配列に格納して、繰り返し処理で座標番号を付与しています。
- 矢印キーを押下したら現在の座標番号を取得し、移動先の座標番号を計算します。
座標計算の例
- 例えば、現在フォーカスしている要素が「ボタン7」の場合 → 座標番号は
1-2
- 矢印キー押下時の座標の増減値と、計算結果(=移動先の座標)は以下の表の通り。
'1-2' 基準の例 | ← | → | ↑ | ↓ |
---|
座標の増減値 | [-1,0] | [1,0] | [0,-1] | [0,1] |
移動先の座標 | '0-2' | '2-2' | '1-1' | '1-3' |
- あとは、例えば
←
キーの場合、data-coord="0-2"
の要素にフォーカスしてやるだけです。
完成コード例
htmlに.focusable-container
とfocusable-item
というclassを追加
html
1<div id="wrapper">
2 <div class="inner_left">
3 <div class="frame_button focusable-container"> <!-- class追加 -->
4 <button class="focusable-item"><span>ボタン1</span></button> <!-- class追加 -->
5 <button class="focusable-item"><span>ボタン2</span></button> <!-- class追加 -->
6 <button class="focusable-item"><span>ボタン3</span></button> <!-- class追加 -->
7 <button class="focusable-item"><span>ボタン4</span></button> <!-- class追加 -->
8 <a href="" class="focusable-item">サブリンク</a> <!-- class追加 -->
9 <div class="focusable-item" tabindex="0">………</div> <!-- class追加 -->
10 </div>
11 </div>
12 <div class="inner_right main_content">
13 <div class="main_inner focusable-container"> <!-- class追加 -->
14 <button class="focusable-item"><span>ボタン5</span></button> <!-- class追加 -->
15 <button class="focusable-item"><span>ボタン6</span></button> <!-- class追加 -->
16 <button class="focusable-item"><span>ボタン7</span></button> <!-- class追加 -->
17 <button class="focusable-item"><span>ボタン8</span></button> <!-- class追加 -->
18 <a class="focusable-item" href="">サブリンク2</a> <!-- class追加 -->
19 <div class="focusable-item" tabindex="0">リンク3</div> <!-- class追加 -->
20 </div>
21 <div class="main_inner focusable-container"> <!-- class追加 -->
22 <button class="focusable-item"><span>ボタン5_2</span></button> <!-- class追加 -->
23 <button class="focusable-item"><span>ボタン6_2</span></button> <!-- class追加 -->
24 <button class="focusable-item"><span>ボタン7_2</span></button> <!-- class追加 -->
25 <button class="focusable-item"><span>ボタン8_2</span></button> <!-- class追加 -->
26 <a class="focusable-item" href="">サブリンク2_2</a> <!-- class追加 -->
27 <div class="focusable-item" tabindex="0">リンク3_2</div> <!-- class追加 -->
28 </div>
29 </div>
30 <div class="inner_right focusable-container"> <!-- class追加 -->
31 <button class="focusable-item"><span>ボタン9</span></button> <!-- class追加 -->
32 <button class="focusable-item"><span>ボタン10</span></button> <!-- class追加 -->
33 <button class="focusable-item"><span>ボタン11</span></button> <!-- class追加 -->
34 <button class="focusable-item"><span>ボタン12</span></button> <!-- class追加 -->
35 <a class="focusable-item" href="">サブリンク4</a> <!-- class追加 -->
36 <div class="focusable-item" tabindex="0">リンク5</div> <!-- class追加 -->
37 </div>
38</div>
cssに以下を追加
css
1*:focus {
2 border: 1px solid blue;
3 border-radius: 0.2em;
4}
javascript
1const wrapper = document.querySelector('#wrapper');
2const focusableContainers = document.querySelectorAll('.focusable-container');
3const focusableItemsMap = [...focusableContainers].map(fc => [...fc.querySelectorAll('.focusable-item')]);
4const focusableItems = focusableItemsMap.flatMap(elems => elems);
5
6window.addEventListener('DOMContentLoaded', setDataCoord, false);
7wrapper.addEventListener('keydown', manipulateFocus, false);
8
9function setDataCoord() {
10 const coords = [...focusableItemsMap].flatMap((elems, x) => elems.map((_, y) => `${x}-${y}`));
11 focusableItems.forEach((elem, i) => elem.dataset.coord = coords[i]);
12}
13
14function manipulateFocus(event) {
15 const keys = {'ArrowLeft': [-1, 0], 'ArrowRight': [1, 0], 'ArrowUp': [0, -1], 'ArrowDown': [0, 1]};
16
17 for (const [key, [x, y]] of Object.entries(keys)) {
18 if (event.key === key) {
19 event.preventDefault();
20 const currentCoord = event.target.dataset.coord;
21 const targetCoord = currentCoord ? currentCoord.replace(/(\d+)-(\d+)/, (_, px, py) => [Number(px) + x, Number(py) + y].join('-')) : '0-0';
22 const [targetElem] = document.querySelectorAll(`[data-coord="${targetCoord}"]`);
23 if (targetElem) targetElem.focus();
24 return;
25 }
26 }
27}
コメントによる解説付きJavaScriptコード
処理の内容をわかりやすくするために、コメントで解説を書きました。
コードの内容は上記のものと全く同じです。
javascript
13
4// #wrapper要素を取得
5const wrapper = document.querySelector('#wrapper');
6
7// .focusable-container要素を取得
8const focusableContainers = document.querySelectorAll('.focusable-container');
9
10// それぞれの.focusable-containerの中身の要素を取得し、containerごとに2次元配列に格納する
11const focusableItemsMap = [...focusableContainers].map(fc => [...fc.querySelectorAll('.focusable-item')]);
12
13// ↑を一次元配列化する
14const focusableItems = focusableItemsMap.flatMap(elems => elems);
15
1618
19window.addEventListener('DOMContentLoaded', setDataCoord, false); // ページロード時
20wrapper.addEventListener('keydown', manipulateFocus, false); // キーを押している間
21
2224
25function setDataCoord() {
26 // 座標を生成
27 const coords = [...focusableItemsMap].flatMap((elems, x) => elems.map((_, y) => `${x}-${y}`));
28
29 // 一旦こういう形の2次元配列を生成してから
30 // [
31 // ['0-0', '0-1', '0-2', '0-3', '0-4', '0-5']
32 // ['1-0', '1-1', '1-2', '1-3', '1-4', '1-5']
33 // ['2-0', '2-1', '2-2', '2-3', '2-4', '2-5']
34 // ['3-0', '3-1', '3-2', '3-3', '3-4', '3-5']
35 // ]
36
37 // 1次元配列化しています
38 // ['0-0', '0-1', '0-2', '0-3', '0-4', '0-5', '1-0', '1-1', '1-2', '1-3', '1-4', '1-5', '2-0', '2-1', '2-2', '2-3', '2-4', '2-5', '3-0', '3-1', '3-2', '3-3', '3-4', '3-5']
39
40 // 生成した座標をデータ属性として、.focusable-itemに付与していく
41 focusableItems.forEach((elem, i) => elem.dataset.coord = coords[i]);
42}
43
4446
47function manipulateFocus(event) {
48
49 // 矢印キーによって加算される座標値
50 const keys = {'ArrowLeft': [-1, 0], 'ArrowRight': [1, 0], 'ArrowUp': [0, -1], 'ArrowDown': [0, 1]};
51
52 // オブジェクトkeysの中身を一つずつ確認
53 for (const [key, [x, y]] of Object.entries(keys)) {
54
55 // 押したキーとオブジェクトkeysのプロパティ名が一致するとき、
56 if (event.key === key) {
57
58 // デフォルトの矢印キーの動きを停止
59 event.preventDefault();
60
61 // 現在フォーカスしている操作ボタンの座標番号を取得
62 const currentCoord = event.target.dataset.coord;
63
64 // 移動先の座標番号を計算(矢印キー押下時、何もフォーカスしていなかったら移動先の座標は'0-0')
65 const targetCoord = currentCoord ? currentCoord.replace(/(\d+)-(\d+)/, (_, px, py) => [Number(px) + x, Number(py) + y].join('-')) : '0-0';
66
67 // 移動先の要素を取得する
68 const [targetElem] = document.querySelectorAll(`[data-coord="${targetCoord}"]`);
69
70 // その座標番号を持つ要素が存在していたら、その要素をフォーカスする
71 if (targetElem) targetElem.focus();
72
73 // ここで繰り返し処理を完全に終了(残りのオブジェクトの中身はチェックされないので無駄な処理を少なく!)
74 return;
75 }
76 }
77}
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2022/10/30 08:17