回答編集履歴

2

大幅に修正しました

2021/03/05 09:23

投稿

babu_babu_baboo
babu_babu_baboo

スコア616

test CHANGED
@@ -1,391 +1,663 @@
1
+ 前回の回答があまりにも適当過ぎたので書き直しました。
2
+
1
- ちょっと大げさかな
3
+ think49 らの指摘で、summary どに対応しました。
2
4
 
3
5
 
4
6
 
5
7
  ```js
6
8
 
7
- <!DOCTYPE html>
8
-
9
- <meta charset="UTF-8">
9
+ <!DOCTYPE html><title></title><meta charset="utf-8">
10
-
11
- <title></title>
12
10
 
13
11
  <style>
14
12
 
13
+ a:focus, datalist:focus, input:focus {
14
+
15
+ background: rgba(255,0,0,.2);
16
+
17
+ }
18
+
15
19
  </style>
16
20
 
17
21
 
18
22
 
19
23
  <body>
20
24
 
25
+ <nav>
26
+
27
+ <p>最初に[Tab]でフォーカスを移動した場合テストできる<br>
28
+
29
+ <a href="#C">ABC</a>
30
+
31
+ <a href="#C">DEF</a>
32
+
33
+ <a href="#A">GHI</a><br>
34
+
35
+ </nav>
36
+
37
+
38
+
39
+ <form>
40
+
41
+ <p>フォーム内のアンカータグを移動する<br>
42
+
43
+ <a href="#C">ABC</a>
44
+
45
+ <a href="#C">DEF</a>
46
+
47
+ <a href="#A">GHI</a>
48
+
49
+
50
+
51
+ <p>通常の INPUT要素<br>
52
+
21
- <form id="hoge">
53
+ 1.<input name="d0" value="">
54
+
22
-
55
+ 2.<input name="d1" value="">
56
+
57
+
58
+
59
+ <p>表示されていない要素<br>
60
+
23
- <input type="text" name="a"><br>
61
+ 1.<input type="hidden" name="d2">
24
-
62
+
25
- <input type="text" name="b" style="display:none;"><br>
63
+ 2.<input type="hidden" name="d2" style="display:none;">
26
-
64
+
27
- <input type="text" name="c"><br>
65
+ 3.<input type="hidden" name="d2" style="visibility:hidden;">
28
-
66
+
29
- <input type="text" name="d" tabindex="-1"><br>
67
+ 4.<input type="hidden" name="d2" style="opacity: 0;">
68
+
69
+
70
+
30
-
71
+ <p>INPUT[type=radio]要素の場合<br>
72
+
73
+ <label>1.<input type="radio" name="d3" value="1">abc</label>
74
+
75
+ <label>2.<input type="radio" name="d3" value="2">def</label>
76
+
77
+ <label>3.<input type="radio" name="d3" value="3">ghi</label>
78
+
79
+
80
+
81
+ <p>INPUT[type=checkbox]要素の場合<br>
82
+
83
+ <label><input type="checkbox" name="d4" value="1">abc</label>
84
+
85
+ <label><input type="checkbox" name="d4" value="2">def</label>
86
+
87
+ <label><input type="checkbox" name="d4" value="3">ghi</label><br>
88
+
89
+
90
+
91
+ <p>SELECT要素<br>
92
+
93
+ <select name="d5"><option value="">-- <option value="1">abc <option value="2">def</select>
94
+
95
+
96
+
97
+ <p>TEXTAREA要素<br>
98
+
31
- <input type="text" name="e"><br>
99
+ <textarea name="d6" cols="80" rows="5">
100
+
101
+ 改行すると、その行の最初のインデントが継続されます
102
+
103
+ 改行せずに移動する場合は[Ctrl]+[Enter]、設定で逆の動作をします
104
+
105
+ 改行すると、その行の最初のインデントが継続されます
106
+
107
+ </textarea>
108
+
109
+
110
+
111
+ <p>DATALIST要素にSUMMARYがある場合<br>
112
+
113
+ <details>
114
+
115
+ <summary>Summary OPEN1</summary>
116
+
117
+ <ol><li>abc <li>def <li>ghi</ol>
118
+
119
+ </details>
120
+
121
+ <details open>
122
+
123
+ <summary>Summary OPEN2</summary>
124
+
125
+ <ol><li>abc <li>def <li>ghi</ol>
126
+
127
+ </details>
128
+
129
+
130
+
131
+ <p>INPUT[type=button] / BUTTON 要素の場合<br>
132
+
133
+ <input type="button" value="フォームの移動を無効にする" onclick="F.disabled=true">
134
+
135
+ <input type="button" value="フォームの移動を有効にする" onclick="F.disabled=false">
136
+
137
+ </p>
138
+
139
+
32
140
 
33
141
  </form>
34
142
 
35
-
36
-
37
-
38
-
39
143
  <script>
40
144
 
145
+
146
+
147
+ /*
148
+
149
+ [Enter] 要素を次に移動する
150
+
151
+ [Shift]+[Enter] 前に移動する
152
+
153
+ [Ctrl]+[Enter]: 作用する
154
+
155
+ */
156
+
157
+
158
+
41
159
  class ExEnter {
42
160
 
43
161
 
44
162
 
45
- constructor (root = document.documentElement, option = { }) {
46
-
47
- this.root = root;
48
-
49
- this.walker = document.createTreeWalker (root, NodeFilter.SHOW_ELEMENT, this.filter, true);
50
-
51
- this.option = Object.assign ({ }, this.constructor.defaultOption, option);
52
-
53
-
54
-
55
- //IME日本語入力中で未変換文字があり Enterが押されたことを感知するには
56
-
57
- //keypress イベントがが実行されないことを利用する
58
-
59
- this.imeFlag = null; //ime
163
+ constructor (root = document.documentElement, option = { }) {
164
+
165
+ const
166
+
167
+ filter = this.filter.bind (this),
168
+
169
+ Nfilter = NodeFilter.SHOW_ELEMENT;
170
+
171
+
172
+
173
+ this.root = root;
174
+
175
+ this.walker = root.ownerDocument.createTreeWalker (root, Nfilter, filter, true);
176
+
177
+ this.option = Object.assign ({ }, this.constructor.defaultOption, option);
178
+
179
+
180
+
181
+ //IME日本語入力中で未変換文字があり Enterが押されたことを感知するには
182
+
183
+ //keypress イベントがが実行されないことを利用する
184
+
185
+ this.imeFlag = null; //ime
186
+
187
+ this.disabled = false; //機能停止用
188
+
189
+ }
190
+
191
+
192
+
193
+
194
+
195
+ //ルートの中の最初の要素を返す
196
+
197
+ get firstElement () {
198
+
199
+ this.walker.currentNode = this.root;
200
+
201
+ return this.walker.firstChild ();
202
+
203
+ }
204
+
205
+
206
+
207
+
208
+
209
+ //ルートの最後の要素を返す
210
+
211
+ get lastElement () {
212
+
213
+ this.walker.currentNode = this.root;
214
+
215
+ return this.walker.lastChild ();
216
+
217
+ }
218
+
219
+
220
+
221
+
222
+
223
+ //要素を移動する
224
+
225
+ move (target, direction = false) {
226
+
227
+ if (this.disabled)
228
+
229
+ return;
230
+
231
+
232
+
233
+ const
234
+
235
+ walker = this.walker,
236
+
237
+ isLoop = this.option.loop;
238
+
239
+
240
+
241
+ walker.currentNode = target;
242
+
243
+ let e = (direction)
244
+
245
+ ? walker.previousNode () || (isLoop ? this.lastElement: null)
246
+
247
+ : walker.nextNode () || (isLoop ? this.firstElement: null);
248
+
249
+
250
+
251
+ if (e) e.focus ();
252
+
253
+ }
254
+
255
+
256
+
257
+
258
+
259
+ //TreeWalker用のフィルター関数(要.bind(this))
260
+
261
+ filter (node) {
262
+
263
+ const
264
+
265
+ accept = NodeFilter.FILTER_ACCEPT,
266
+
267
+ skip = NodeFilter.FILTER_SKIP;
268
+
269
+
270
+
271
+ switch (node.nodeName) {
272
+
273
+ case 'INPUT' : case 'TEXTAREA' : case 'SELECT' : case 'BUTTON' :
274
+
275
+ if (node.disabled) break;
276
+
277
+ if (node.readOnly) break;
278
+
279
+ if (this.option.tabIndex && ('-1' === node.getAttribute ('tabIndex'))) break;
280
+
281
+ if (isHide (node)) break;
282
+
283
+ return accept;
284
+
285
+
286
+
287
+ case 'A' : //href = '#...' で始まるもののみ対象とする
288
+
289
+ if (this.option.anchor) {
290
+
291
+ let href = node.getAttribute ('href');
292
+
293
+ if (href)
294
+
295
+ if (! href.indexOf ('#'))
296
+
297
+ return accept;
298
+
299
+ }
300
+
301
+ break;
302
+
303
+
304
+
305
+ case 'SUMMARY' :
306
+
307
+ return accept;
308
+
309
+ }
310
+
311
+ return skip;
312
+
313
+
314
+
315
+ //__
316
+
317
+ //祖先の要素が隠された状態にあるか?
318
+
319
+ function isHide (node) {
320
+
321
+ const chks = ['display', 'visibility', 'opacity', 'height', 'width'];
322
+
323
+
324
+
325
+ for (let e = node; e !== document.body; e = e.parentNode) {
326
+
327
+ let cs = getComputedStyle (e, null);
328
+
329
+ let [d, v, o, h, w] = chks.map (p=> cs.getPropertyValue (p));
330
+
331
+ if (
332
+
333
+ 'none' === d || 'hidden' === v || 0 == parseFloat (o) ||
334
+
335
+ ! ('auto' === h || 0 < parseInt (h, 10)) ||
336
+
337
+ ! ('auto' === w || 0 < parseInt (w, 10))
338
+
339
+ ) return true;
340
+
341
+ }
342
+
343
+ return false;
344
+
345
+ }
346
+
347
+ }
348
+
349
+
350
+
351
+
352
+
353
+ //_____
354
+
355
+
356
+
357
+ //[CTRL]が押されていれば、適用させる
358
+
359
+ apply (e, sw, prev) {
360
+
361
+ let stay = false, tag = e.nodeName;
362
+
363
+
364
+
365
+ if ('TEXTAREA' === tag && !prev) {
366
+
367
+ if (sw ^ this.option.textarea) {
368
+
369
+ this.constructor.insertCRLF (e, this.option.autoIndent);
370
+
371
+ stay = true;
372
+
373
+ }
374
+
375
+ }
376
+
377
+ else if (sw) {
378
+
379
+ stay = this.option.stay;
380
+
381
+ switch (tag) {
382
+
383
+ case 'SUMMARY' :
384
+
385
+ let parent = e.parentNode;
386
+
387
+ parent.hasAttribute ('open')
388
+
389
+ ? parent.removeAttribute ('open')
390
+
391
+ : parent.setAttribute ('open', '');
392
+
393
+ break;
394
+
395
+
396
+
397
+ case 'A' :
398
+
399
+ fireEvent (e);
400
+
401
+ break;
402
+
403
+
404
+
405
+ case 'INPUT' :
406
+
407
+ switch (e.type) {
408
+
409
+ case 'radio': case 'checkbox' :
410
+
411
+ e.checked = !e.checked;
412
+
413
+ break;
414
+
415
+
416
+
417
+ case 'button' : case 'submit' : case 'reset' :
418
+
419
+ fireEvent (e);
420
+
421
+ break;
422
+
423
+ }
424
+
425
+ break;
426
+
427
+ }
428
+
429
+ }
430
+
431
+ //値が有効か?
432
+
433
+ if (this.option.invalidStop && e.checkValidity)
434
+
435
+ stay = ! e.checkValidity ();
436
+
437
+
438
+
439
+ return stay;
440
+
441
+
442
+
443
+ //__
444
+
445
+
446
+
447
+ function fireEvent (e) {
448
+
449
+ let event = document.createEvent ('MouseEvents');
450
+
451
+ event.initEvent ('click', false, true);
452
+
453
+ e.dispatchEvent (event);
454
+
455
+ return event;
456
+
457
+ }
458
+
459
+ }
460
+
461
+
462
+
463
+
464
+
465
+ //_____
466
+
467
+
468
+
469
+ //イベントハンドラ(各typeによって分岐)
470
+
471
+ handleEvent (event) {
472
+
473
+ this[event.type + 'Handler'].call (this, event);
474
+
475
+ }
476
+
477
+
478
+
479
+ //キーアップハンドラ
480
+
481
+ keyupHandler (event) {
482
+
483
+ let { code, shiftKey, ctrlKey, target: e } = event;
484
+
485
+ if ('Enter' !== code) return;
486
+
487
+ if (! this.imeFlag) //IMEの動作で未変換中の文字があると判断してスルー
488
+
489
+ return event.preventDefault ();
490
+
491
+ this.imeFlag = null;
492
+
493
+ if (! this.apply (e, ctrlKey, shiftKey))
494
+
495
+ this.move (e, shiftKey);
496
+
497
+ }
498
+
499
+
500
+
501
+ //キープレスハンドラ
502
+
503
+ keypressHandler (event) {
504
+
505
+ let { target: { nodeName: n, type: t }, code } = event;
506
+
507
+ if ('Enter' == code)
508
+
509
+ if (/^(submit|reset|button|textarea)$/.test (t) || 'SUMMARY' === n || 'A' == n)
510
+
511
+ event.preventDefault ();
512
+
513
+ this.imeFlag = true;//未変換文字がある場合の対処として
514
+
515
+ }
516
+
517
+
518
+
519
+ //________
520
+
521
+
522
+
523
+ //インスタンスの作成
524
+
525
+ static create (root = document.documentElement, option = { }) {
526
+
527
+ let obj = new this (root, option);
528
+
529
+ root.addEventListener ('keyup', obj, false);
530
+
531
+ root.addEventListener ('keypress', obj, false);
532
+
533
+ return obj;
534
+
535
+ }
536
+
537
+
538
+
539
+ //textarea要素に改行を挿入する
540
+
541
+ static insertCRLF (t, autoIndent = false, start = t.selectionStart || 0) {
542
+
543
+ let
544
+
545
+ str = '\n',
546
+
547
+ value = t.value,
548
+
549
+ first = value.slice (0, start),
550
+
551
+ last = value.slice (start).replace (/^([\t\u3000\u0020]+)/, '');
552
+
553
+
554
+
555
+ if (autoIndent) {
556
+
557
+ let spc = /(?:\n|^)([\t\u3000\u0020]+).*$/.exec (first);
558
+
559
+ if (spc) str += spc[1];
560
+
561
+ }
562
+
563
+
564
+
565
+ t.value = first + str + last;
566
+
567
+ t.selectionStart = t.selectionEnd = start + str.length;
568
+
569
+ }
570
+
571
+
572
+
573
+
574
+
575
+
576
+
577
+ //初期設定オプション値
578
+
579
+ static defaultOption = {
580
+
581
+ stay: true, //true: radio,checkbox,submit,button,summary を作用させた後も留まる
582
+
583
+ loop: true, //要素を巡回する
584
+
585
+ tabIndex: true, //tabIndex="-1" を有効にする
586
+
587
+ anchor: true, //アンカータグを有効にする(但し href属性の値が "#"から始まる場合)
588
+
589
+ textarea: true, //true:テキストエリア内では改行する, false:次に移動する(改行は、[Ctrl]+[Enter])
590
+
591
+ autoIndent: true, //textarea要素内でのオートインデントを行う
592
+
593
+ invalidStop: false,//要素の値が不正な場合でも移動する
594
+
595
+ };
60
596
 
61
597
  }
62
598
 
63
599
 
64
600
 
65
-
601
+ const
602
+
66
-
603
+ nav = ExEnter.create (document.querySelector ('nav')),
604
+
605
+ F = ExEnter.create (document.querySelector ('form'));
606
+
607
+
608
+
67
- //ルートの中の最初の要素を返す
609
+ </script>
610
+
611
+
612
+
613
+
614
+
68
-
615
+ ```
616
+
617
+
618
+
619
+ ```js
620
+
621
+ <a href="#"><h3 class="LC20lb"><span>BELIEVE</span></h3></a>
622
+
623
+ <a href="#"><h3 class="LC20lb"><span>BELIEVE</span></h3></a>
624
+
625
+ <a href="#"><h3 class="LC20lb"><span>BELIEVE</span></h3></a>
626
+
627
+ <a href="#"><h3 class="LC20lb"><span>BELIEVE</span></h3></a>
628
+
629
+
630
+
631
+
632
+
633
+ <script>
634
+
635
+ const walker = document.createTreeWalker (document.body, NodeFilter.SHOW_ELEMENT, filter, true);
636
+
637
+
638
+
69
- get firstElement () {
639
+ function filter (node) {
70
-
71
- this.walker.currentNode = this.root;
640
+
72
-
73
- return this.walker.firstChild ();
641
+ return 'A' === node.nodeName
642
+
643
+ ? NodeFilter.FILTER_ACCEPT
644
+
645
+ : NodeFilter.FILTER_SKIP;
74
646
 
75
647
  }
76
648
 
77
649
 
78
650
 
79
-
80
-
81
- //ルートの最後の要素を返す
82
-
83
- get lastElement () {
84
-
85
- this.walker.currentNode = this.root;
651
+ function handler ({ ctrlKey, key, target: e }, n) {
86
-
652
+
87
- return this.walker.lastChild ();
653
+ (ctrlKey && 'ArrowDown' === key) &&
654
+
655
+ (walker.currentNode = document.activeElement, n = walker.nextNode (), n && n.focus ());
88
656
 
89
657
  }
90
658
 
91
659
 
92
660
 
93
-
94
-
95
- //要素を移動する
96
-
97
- move (target, direction = false) {
98
-
99
- const
100
-
101
- walker = this.walker,
102
-
103
- isLoop = this.option.loop;
104
-
105
-
106
-
107
- this.walker.currentNode = target;
108
-
109
- let e = (direction)
110
-
111
- ? walker.previousNode () || (isLoop ? this.lastElement: null)
112
-
113
- : walker.nextNode () || (isLoop ? this.firstElement: null);
114
-
115
- e && e.focus ();
116
-
117
- }
118
-
119
-
120
-
121
-
122
-
123
- filter (node) {
124
-
125
- if (/^(INPUT|TEXTAREA|SELECT|BUTTON)$/.test (node.tagName))
126
-
127
- if (! node.disabled && ! node.readOnly && '-1' != node.getAttribute ('tabIndex')) {
128
-
129
- if (! isHide (node))
130
-
131
- return NodeFilter.FILTER_ACCEPT;
132
-
133
- }
134
-
135
- return NodeFilter.FILTER_SKIP;
136
-
137
- //__
138
-
139
- function isHide (node) {
140
-
141
- for (let e = node, s; e; e = e.parentNode) {
142
-
143
- if (e === document.body) break;
144
-
145
- s = getComputedStyle (e, null);
146
-
147
- if ('none' === s.display
148
-
149
- || 'hidden' === s.visibility
150
-
151
- || !parseFloat (s.opacity || '')
152
-
153
- || !parseInt (s.height || '', 10)
154
-
155
- || !parseInt (s.width || '', 10)
156
-
157
- ) return true;
158
-
159
- }
160
-
161
- return false;
162
-
163
- }
164
-
165
- }
166
-
167
-
168
-
169
-
170
-
171
- //_____
172
-
173
-
174
-
175
- //テキストエリアのキャレットの位置で分割(改行)する
176
-
177
- textareaSpliter (e) {
178
-
179
- let
180
-
181
- v = e.value,
182
-
183
- a = v.substring (0, e.selectionStart);
184
-
185
-
186
-
187
- e.value = a + '\n' + v.substr (e.selectionEnd);
188
-
189
- e.selectionStart = e.selectionEnd = a.length + 1;
190
-
191
- }
192
-
193
-
194
-
195
-
196
-
197
- //[CTRL]が押されていれば、適用させる
198
-
199
- apply (e, sw, prev) {
200
-
201
- let r = false; //要素に対して操作を行ったか
202
-
203
- switch (e.type) {
204
-
205
- case 'radio': case 'checkbox' :
206
-
207
- if (sw) {
208
-
209
- e.checked = !e.checked;
210
-
211
- r = this.option.stay;
212
-
213
- }
214
-
215
- break;
216
-
217
- case 'button' : case 'submit' : case 'reset' :
218
-
219
- if (sw) {
220
-
221
- let event = document.createEvent ('MouseEvents');
222
-
223
- event.initEvent("click", false, true);
224
-
225
- e.dispatchEvent (event);
226
-
227
- r = this.option.stay;
228
-
229
- }
230
-
231
- break;
232
-
233
- case 'textarea' :
234
-
235
- if ((sw ^ this.option.textarea_enter) && !prev) {
236
-
237
- this.textareaSpliter (e);
238
-
239
- r =true;
240
-
241
- }
242
-
243
- break;
244
-
245
- }
246
-
247
- return r;
248
-
249
- }
250
-
251
-
252
-
253
-
254
-
255
- //_____
256
-
257
-
258
-
259
- handleEvent (event) {
260
-
261
- this[event.type+ 'Handler'].call (this, event, event.target);
262
-
263
- }
264
-
265
-
266
-
267
- keyupHandler (event, e) {
268
-
269
- let { code, shiftKey, ctrlKey } = event;
270
-
271
- if ('Enter' !== code) return;
272
-
273
- if (! this.imeFlag) //IMEの動作で未変換中の文字があると判断してスルー
274
-
275
- return event.preventDefault ();
276
-
277
- this.imeFlag = null;
278
-
279
- if (! this.apply (e, ctrlKey, shiftKey))
280
-
281
- this.move (e, shiftKey);
282
-
283
- }
284
-
285
-
286
-
287
- keypressHandler (event, e) {
288
-
289
- if (/^(submit|reset|button|textarea)$/.test (e.type))
290
-
291
- event.preventDefault ();
292
-
293
- this.imeFlag = true;//未変換文字がある場合の対処として
294
-
295
- }
296
-
297
-
298
-
299
- //________
300
-
301
-
302
-
303
- static defaultOption = {
304
-
305
- stay: false, //true: radio,checkbox,submit,button を作用させた後に自動的に移動する
306
-
307
- textarea_enter: false, //true:テキストエリア内では改行する ,false:[Ctrl]+[Enter]で改行
308
-
309
- loop: true, //要素を巡回する
310
-
311
- };
312
-
313
-
314
-
315
-
316
-
317
- static create (root = document.documentElement, option = { }) {
318
-
319
- let obj = new this (root, option);
320
-
321
- root.addEventListener ('keyup', obj, false);
661
+ document.addEventListener ('keydown', handler, true);
322
-
323
- root.addEventListener ('keypress', obj, false);
324
-
325
- return obj;
326
-
327
- }
328
-
329
- }
330
-
331
-
332
-
333
- ExEnter.create ();
334
-
335
-
336
-
337
- </script>
338
-
339
-
340
-
341
-
342
662
 
343
663
  ```
344
-
345
-
346
-
347
- ```js
348
-
349
- <a href="#"><h3 class="LC20lb"><span>BELIEVE</span></h3></a>
350
-
351
- <a href="#"><h3 class="LC20lb"><span>BELIEVE</span></h3></a>
352
-
353
- <a href="#"><h3 class="LC20lb"><span>BELIEVE</span></h3></a>
354
-
355
- <a href="#"><h3 class="LC20lb"><span>BELIEVE</span></h3></a>
356
-
357
-
358
-
359
-
360
-
361
- <script>
362
-
363
- const walker = document.createTreeWalker (document.body, NodeFilter.SHOW_ELEMENT, filter, true);
364
-
365
-
366
-
367
- function filter (node) {
368
-
369
- return 'A' === node.nodeName
370
-
371
- ? NodeFilter.FILTER_ACCEPT
372
-
373
- : NodeFilter.FILTER_SKIP;
374
-
375
- }
376
-
377
-
378
-
379
- function handler ({ ctrlKey, key, target: e }, n) {
380
-
381
- (ctrlKey && 'ArrowDown' === key) &&
382
-
383
- (walker.currentNode = document.activeElement, n = walker.nextNode (), n && n.focus ());
384
-
385
- }
386
-
387
-
388
-
389
- document.addEventListener ('keydown', handler, true);
390
-
391
- ```

1

汎用性なし

2021/03/05 09:23

投稿

babu_babu_baboo
babu_babu_baboo

スコア616

test CHANGED
@@ -341,3 +341,51 @@
341
341
 
342
342
 
343
343
  ```
344
+
345
+
346
+
347
+ ```js
348
+
349
+ <a href="#"><h3 class="LC20lb"><span>BELIEVE</span></h3></a>
350
+
351
+ <a href="#"><h3 class="LC20lb"><span>BELIEVE</span></h3></a>
352
+
353
+ <a href="#"><h3 class="LC20lb"><span>BELIEVE</span></h3></a>
354
+
355
+ <a href="#"><h3 class="LC20lb"><span>BELIEVE</span></h3></a>
356
+
357
+
358
+
359
+
360
+
361
+ <script>
362
+
363
+ const walker = document.createTreeWalker (document.body, NodeFilter.SHOW_ELEMENT, filter, true);
364
+
365
+
366
+
367
+ function filter (node) {
368
+
369
+ return 'A' === node.nodeName
370
+
371
+ ? NodeFilter.FILTER_ACCEPT
372
+
373
+ : NodeFilter.FILTER_SKIP;
374
+
375
+ }
376
+
377
+
378
+
379
+ function handler ({ ctrlKey, key, target: e }, n) {
380
+
381
+ (ctrlKey && 'ArrowDown' === key) &&
382
+
383
+ (walker.currentNode = document.activeElement, n = walker.nextNode (), n && n.focus ());
384
+
385
+ }
386
+
387
+
388
+
389
+ document.addEventListener ('keydown', handler, true);
390
+
391
+ ```