탭 인덱스에서 다음 요소에 초점 맞추기


103

포커스가있는 현재 요소를 기반으로 탭 시퀀스의 다음 요소로 포커스를 이동하려고합니다. 지금까지 검색에서 아무것도 찾지 못했습니다.

function OnFocusOut()
{
    var currentElement = $get(currentElementId); // ID set by OnFocusIn 

    currentElementId = "";
    currentElement.nextElementByTabIndex.focus();
}

물론 nextElementByTabIndex가 이것이 작동하는 핵심 부분입니다. 탭 시퀀스에서 다음 요소를 어떻게 찾습니까? 솔루션은 JQuery와 같은 것이 아니라 JScript를 기반으로해야합니다.


3
왜이 라인이 currentElementId = "";있습니까?

1
어떤 브라우저도 탭 순서 정보를 노출한다고 생각하지 않습니다. 브라우저 자체에서 사용하는 알고리즘은 복제하기 너무 복잡합니다. 어쩌면 당신은 예를 들어, 귀하의 요구 사항을 제한 할 수 있습니다 "만을 고려 input, button그리고 textarea태그와 무시 tabindex속성을".
Wladimir Palant 2011 년

.newElementByTabIndex작동하지 않는 코드이므로 코드 를 확인해야 합니다.
0x499602D2

2
다시 말하지만, 특정 태그에 대한 제한이 필요하지 않을 수 focus()있습니다. 메소드가 존재 하는지 확인할 수 있습니다.
Wladimir Palant

1
@David 그것은 존재하지 않는 기능이므로 내 질문입니다. : D
JadziaMD 2011-08-26

답변:


23

jquery없이 : 우선, 탭 가능한 요소에 class="tabable"이것을 추가 하면 나중에 선택할 수 있습니다. (아래 코드에서 "."클래스 선택기 접두사를 잊지 마십시오)

var lastTabIndex = 10;
function OnFocusOut()
{
    var currentElement = $get(currentElementId); // ID set by OnFOcusIn
    var curIndex = currentElement.tabIndex; //get current elements tab index
    if(curIndex == lastTabIndex) { //if we are on the last tabindex, go back to the beginning
        curIndex = 0;
    }
    var tabbables = document.querySelectorAll(".tabable"); //get all tabable elements
    for(var i=0; i<tabbables.length; i++) { //loop through each element
        if(tabbables[i].tabIndex == (curIndex+1)) { //check the tabindex to see if it's the element we want
            tabbables[i].focus(); //if it's the one we want, focus it and exit the loop
            break;
        }
    }
}

16
모든 요소에 이름을 추가 할 필요가없는 솔루션이 이상적 일 것입니다.
JadziaMD

3
좋아요, 이건 양식인가요? 원하는 모든 요소가 입력 요소 인 경우 대신 줄 var tabbables = document.getElementsByName("tabable");을 바꿀 수 있습니다var tabbables = document.getElementsByTagName("input");
Brian Glaz 2011-08-26

var tabbables = document.querySelectorAll("input, textarea, button")// IE8 +, HTML을 수정하지 않고 모든 탭 가능 항목에 대한 참조를 가져옵니다.
그렉

2
클래스 대신 사용하는 것보다 이름 속성 = "tabbable"
크리스 F 캐롤

4
flexbox를 사용하면 요소의 순서가 DOM에서 브라우저에서와 시각적으로 다릅니다. flexbox를 사용하여 요소의 순서를 변경할 때 다음 탭 가능한 요소를 선택하는 것만으로는 작동하지 않습니다.
Haneev

75

나는 이것을 구현 한 적이 없지만 비슷한 문제를 조사했으며 여기에 내가 시도 할 것입니다.

먼저 시도

당신은 단순히 수 있다면 첫째, 나는 볼 것이다 화재 keypress이벤트를 현재 포커스가있는 요소에 탭 키를. 브라우저마다이 작업을 수행하는 다른 방법이있을 수 있습니다.

그래도 안된다면 더 열심히해야합니다 ...

jQuery 구현을 참조하려면 다음을 수행해야합니다.

  1. Tab 및 Shift + Tab 듣기
  2. 탭 가능한 요소 파악
  3. 탭 순서 작동 방식 이해

1. Tab 및 Shift + Tab 듣기

Tab 및 Shift + Tab 듣기는 웹의 다른 곳에서 잘 다루어 질 수 있으므로 해당 부분을 건너 뛰겠습니다.

2. 탭 가능한 요소 파악

탭이 가능한 요소를 아는 것은 더 까다 롭습니다. 기본적으로 요소는 포커스가 있고 속성이 tabindex="-1"설정 되지 않은 경우 탭이 가능합니다 . 따라서 어떤 요소가 초점을 맞출 수 있는지 물어봐야합니다. 다음 요소에 초점을 맞출 수 있습니다.

  • input, select, textarea, button, 및 object요소 해제되지 않은.
  • a및 세트에 대한 숫자 값이 있거나있는 area요소 .hreftabindex
  • tabindex세트에 대한 숫자 값이있는 모든 요소 .

또한 다음과 같은 경우에만 요소에 초점을 맞출 수 있습니다.

  • 그 조상은 없습니다 display: none.
  • 의 계산 된 값은 visibility입니다 visible. 즉, visibility설정 한 가장 가까운 조상 은 값이이어야합니다 visible. 조상이 visibility설정 되지 않은 경우 계산 된 값은 visible입니다.

자세한 내용은 다른 Stack Overflow 답변에 있습니다.

3. 탭 순서 작동 방식 이해

문서에있는 요소의 탭 순서는 tabindex속성에 의해 제어됩니다 . 값이 설정되지 않은 경우은 tabindex사실상 0입니다.

tabindex문서 의 순서는 1, 2, 3,…, 0입니다.

처음에 body요소 (또는 요소 없음)에 포커스가 있을 때 탭 순서의 첫 번째 요소는 0이 아닌 가장 낮은 요소입니다 tabindex. 여러 요소가 동일한 tabindex경우 마지막 요소에 도달 할 때까지 문서 순서대로 이동합니다 tabindex. 그런 다음 다음으로 낮은 값으로 이동 tabindex하고 프로세스가 계속됩니다. 마지막으로 0 (또는 비어 있음)으로 해당 요소로 마무리 tabindex합니다.


37

이 목적을 위해 내가 만든 것입니다.

focusNextElement: function () {
    //add all elements we want to include in our selection
    var focussableElements = 'a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled]), [tabindex]:not([disabled]):not([tabindex="-1"])';
    if (document.activeElement && document.activeElement.form) {
        var focussable = Array.prototype.filter.call(document.activeElement.form.querySelectorAll(focussableElements),
        function (element) {
            //check for visibility while always include the current activeElement 
            return element.offsetWidth > 0 || element.offsetHeight > 0 || element === document.activeElement
        });
        var index = focussable.indexOf(document.activeElement);
        if(index > -1) {
           var nextElement = focussable[index + 1] || focussable[0];
           nextElement.focus();
        }                    
    }
}

풍모:

  • 구성 가능한 포커스 가능 요소 집합
  • jQuery가 필요하지 않습니다.
  • 모든 최신 브라우저에서 작동
  • 빠르고 가벼운

2
이것은 가장 효과적이고 자원 친화적 인 솔루션입니다. 감사합니다! 내 전체 작업 스크립트는 다음과 같습니다. stackoverflow.com/a/40686327/1589669
eapo

나는 명시에 tabIndex focussable.sort (sort_by_TabIndex)에 의해 정렬 포함 아래 니펫을 추가했습니다
DavB.cs

1
최고! 그것은 매우 복잡 해야합니다 : nextElementSibling초점을 맞출 수 없을 수도 있고, 다음 초점을 맞출 수있는 것은 형제가 아닐 수도 있습니다.
Tinmarino

좋은 방법,하지만 형태가 아닌 모든 입력을 허용해야 hidden하고 또한 포함 textarea하고 select.
Lucero

23

이 작업을 수행하는 간단한 jQuery 플러그인 을 만들었습니다 . jQuery UI의 ': tabbable'선택기를 사용하여 다음 'tabbable'요소를 찾아 선택합니다.

사용 예 :

// Simulate tab key when element is clicked 
$('.myElement').bind('click', function(event){
    $.tabNext();
    return false;
});

8

대답의 핵심은 다음 요소를 찾는 데 있습니다.

  function findNextTabStop(el) {
    var universe = document.querySelectorAll('input, button, select, textarea, a[href]');
    var list = Array.prototype.filter.call(universe, function(item) {return item.tabIndex >= "0"});
    var index = list.indexOf(el);
    return list[index + 1] || list[0];
  }

용법:

var nextEl = findNextTabStop(element);
nextEl.focus();

우선 순위를 지정하는 데 관심이 없습니다 tabIndex.


3
tabindex 순서가 문서 순서에 위배되면 어떻게됩니까? 나는 배열은 다음 문서 순서의 tabindex 번호로 정렬 할 것 같아요
크리스 F 캐롤

네, 더 "사양을 준수하는"것입니다. 부모 요소 등과 관련된 가장자리 케이스에 대해 잘 모르겠습니다.
André

이러한 태그 중 하나가 아닌 항목에 tabindex 속성이 있으면 어떻게됩니까?
Matt Pennington

1
@MattPennington 무시됩니다. 필터는 검색 속도를 높이기위한 시도입니다.
André Werlang

3

위의 주석에서 언급했듯이 어떤 브라우저도 탭 순서 정보를 노출하지 않는다고 생각합니다. 다음은 탭 순서에서 다음 요소를 가져 오기 위해 브라우저가 수행하는 작업의 단순화 된 근사치입니다.

var allowedTags = {input: true, textarea: true, button: true};

var walker = document.createTreeWalker(
  document.body,
  NodeFilter.SHOW_ELEMENT,
  {
    acceptNode: function(node)
    {
      if (node.localName in allowedTags)
        return NodeFilter.FILTER_ACCEPT;
      else
        NodeFilter.FILTER_SKIP;
    }
  },
  false
);
walker.currentNode = currentElement;
if (!walker.nextNode())
{
  // Restart search from the start of the document
  walker.currentNode = walker.root;
  walker.nextNode();
}
if (walker.currentNode && walker.currentNode != walker.root)
  walker.currentNode.focus();

이것은 일부 태그 만 고려하고 tabindex속성을 무시 하지만 달성하려는 것에 따라 충분할 수 있습니다.


3

tabIndex요소 의 속성을 확인하여 초점을 맞출 수 있는지 확인할 수있는 것 같습니다 . 초점을 맞출 수없는 요소 tabindex는 "-1"입니다.

그런 다음 탭 중지에 대한 규칙을 알아야합니다.

  • tabIndex="1" 우선 순위가 가장 높습니다.
  • tabIndex="2" 다음으로 높은 우선 순위가 있습니다.
  • tabIndex="3" 다음입니다.
  • tabIndex="0" (또는 기본적으로 탭 가능) 우선 순위가 가장 낮습니다.
  • tabIndex="-1" (또는 기본적으로 탭할 수 없음)은 탭 정지 역할을하지 않습니다.
  • tabIndex가 동일한 두 요소의 경우 DOM에서 가장 먼저 나타나는 요소가 더 높은 우선 순위를 갖습니다.

다음은 순수 자바 스크립트를 사용하여 순서대로 탭 정지 목록을 작성하는 방법의 예입니다.

function getTabStops(o, a, el) {
    // Check if this element is a tab stop
    if (el.tabIndex > 0) {
        if (o[el.tabIndex]) {
            o[el.tabIndex].push(el);
        } else {
            o[el.tabIndex] = [el];
        }
    } else if (el.tabIndex === 0) {
        // Tab index "0" comes last so we accumulate it seperately
        a.push(el);
    }
    // Check if children are tab stops
    for (var i = 0, l = el.children.length; i < l; i++) {
        getTabStops(o, a, el.children[i]);
    }
}

var o = [],
    a = [],
    stops = [],
    active = document.activeElement;

getTabStops(o, a, document.body);

// Use simple loops for maximum browser support
for (var i = 0, l = o.length; i < l; i++) {
    if (o[i]) {
        for (var j = 0, m = o[i].length; j < m; j++) {
            stops.push(o[i][j]);
        }
    }
}
for (var i = 0, l = a.length; i < l; i++) {
    stops.push(a[i]);
}

먼저 DOM을 살펴보고 인덱스와 함께 모든 탭 정지를 순서대로 수집합니다. 그런 다음 최종 목록을 조합합니다. tabIndex="0"목록 맨 끝에 있는 항목을 추가합니다 .tabIndex 1, 2, 3 등

"enter"키를 사용하여 탭할 수있는 완전히 작동하는 예제는이 fiddle을 확인하십시오 .


2

Tabbable는 당신에게 모든 tabbable 요소의 목록 제공하는 작은 JS 패키지 탭 순서를 . 따라서 해당 목록에서 요소를 찾은 다음 다음 목록 항목에 집중할 수 있습니다.

패키지는 다른 답변에 언급 된 복잡한 엣지 케이스를 올바르게 처리합니다 (예 : 조상이 display: none ). 그리고 그것은 jQuery에 의존하지 않습니다!

이 글을 쓰는 시점 (버전 1.1.1)에서는 IE8을 지원하지 않으며 브라우저 버그로 인해 contenteditable올바르게 처리되지 않는다는 경고가 있습니다 .


2
function focusNextElement(){
  var focusable = [].slice.call(document.querySelectorAll("a, button, input, select, textarea, [tabindex], [contenteditable]")).filter(function($e){
    if($e.disabled || ($e.getAttribute("tabindex") && parseInt($e.getAttribute("tabindex"))<0)) return false;
    return true;
  }).sort(function($a, $b){
    return (parseFloat($a.getAttribute("tabindex") || 99999) || 99999) - (parseFloat($b.getAttribute("tabindex") || 99999) || 99999);
  });
  var focusIndex = focusable.indexOf(document.activeElement);
  if(focusable[focusIndex+1]) focusable[focusIndex+1].focus();
};

1

이것은 SO에 대한 첫 번째 게시물이므로 수락 된 답변에 대해 충분한 평판을 얻지 못했지만 코드를 다음과 같이 수정해야했습니다.

export function focusNextElement () {
  //add all elements we want to include in our selection
  const focussableElements = 
    'a:not([disabled]), button:not([disabled]), input[type=text]:not([disabled])'
  if (document.activeElement && document.activeElement.form) {
      var focussable = Array.prototype.filter.call(
        document.activeElement.form.querySelectorAll(focussableElements),
      function (element) {
          // if element has tabindex = -1, it is not focussable
          if ( element.hasAttribute('tabindex') && element.tabIndex === -1 ){
            return false
          }
          //check for visibility while always include the current activeElement 
          return (element.offsetWidth > 0 || element.offsetHeight > 0 || 
            element === document.activeElement)
      });
      console.log(focussable)
      var index = focussable.indexOf(document.activeElement);
      if(index > -1) {
         var nextElement = focussable[index + 1] || focussable[0];
         console.log(nextElement)
         nextElement.focus()
      }                    
  }
}

var를 상수로 변경하는 것은 중요하지 않습니다. 주요 변경 사항은 tabindex! = "-1"을 확인하는 선택기를 제거한다는 것입니다. 그런 다음 나중에 요소에 tabindex 속성이 있고 "-1"로 설정되어 있으면 포커스 가능하다고 간주하지 않습니다.

이것을 변경해야하는 이유는 tabindex = "-1"을에 추가 할 <input>때이 요소가 "input [type = text] : not ([disabled])"선택기와 일치하기 때문에 여전히 포커스 가능한 것으로 간주 되었기 때문입니다. 내 변경 사항은 "비활성화 된 텍스트 입력이고 tabIndex 속성이 있고 해당 속성의 값이 -1 인 경우 포커스 가능한 것으로 간주되지 않아야합니다.

나는 수락 된 답변의 저자가 tabIndex 속성에 대한 답변을 편집했을 때 올바르게 수정하지 않았다고 생각합니다. 그렇지 않은 경우 알려주십시오.


1

의 tabindex의 구성 요소에 설정할 수있는 속성. 하나를 선택하고 탭을 누를 때 입력 구성 요소가 반복되어야하는 순서를 지정합니다. 0보다 큰 값은 사용자 정의 탐색을 위해 예약되어 있고, 0은 "자연 순서"(따라서 첫 번째 요소에 대해 설정된 경우 다르게 동작 함), -1은 키보드 포커스를 사용할 수 없음을 의미합니다.

<!-- navigate with tab key: -->
<input tabindex="1" type="text"/>
<input tabindex="2" type="text"/>

또한 텍스트 입력 필드 이외의 다른 항목에 대해 설정할 수도 있지만 어떤 경우에도 거기에서 무엇을 할 것인지는 분명하지 않습니다. 내비게이션이 작동하더라도 매우 명백한 사용자 입력 요소 이외의 다른 항목에는 "자연 순서"를 사용하는 것이 더 좋습니다.

아니요,이 사용자 지정 탐색 경로를 지원하기 위해 JQuery 또는 스크립팅이 전혀 필요하지 않습니다. JavaScript 지원없이 서버 측에서 구현할 수 있습니다. 다른 측면에서 속성은 React 프레임 워크에서도 잘 작동하지만 필요하지 않습니다.


0

다음 요소에 초점을 맞춘보다 완전한 버전입니다. 사양 지침을 따르고 tabindex를 사용하여 요소 목록을 올바르게 정렬합니다. 또한 이전 요소를 얻으려면 반전 변수가 정의됩니다.

function focusNextElement( reverse, activeElem ) {
  /*check if an element is defined or use activeElement*/
  activeElem = activeElem instanceof HTMLElement ? activeElem : document.activeElement;

  let queryString = [
      'a:not([disabled]):not([tabindex="-1"])',
      'button:not([disabled]):not([tabindex="-1"])',
      'input:not([disabled]):not([tabindex="-1"])',
      'select:not([disabled]):not([tabindex="-1"])',
      '[tabindex]:not([disabled]):not([tabindex="-1"])'
      /* add custom queries here */
    ].join(','),
    queryResult = Array.prototype.filter.call(document.querySelectorAll(queryString), elem => {
      /*check for visibility while always include the current activeElement*/
      return elem.offsetWidth > 0 || elem.offsetHeight > 0 || elem === activeElem;
    }),
    indexedList = queryResult.slice().filter(elem => {
      /* filter out all indexes not greater than 0 */
      return elem.tabIndex == 0 || elem.tabIndex == -1 ? false : true;
    }).sort((a, b) => {
      /* sort the array by index from smallest to largest */
      return a.tabIndex != 0 && b.tabIndex != 0 
        ? (a.tabIndex < b.tabIndex ? -1 : b.tabIndex < a.tabIndex ? 1 : 0) 
        : a.tabIndex != 0 ? -1 : b.tabIndex != 0 ? 1 : 0;
    }),
    focusable = [].concat(indexedList, queryResult.filter(elem => {
      /* filter out all indexes above 0 */
      return elem.tabIndex == 0 || elem.tabIndex == -1 ? true : false;
    }));

  /* if reverse is true return the previous focusable element
     if reverse is false return the next focusable element */
  return reverse ? (focusable[focusable.indexOf(activeElem) - 1] || focusable[focusable.length - 1]) 
    : (focusable[focusable.indexOf(activeElem) + 1] || focusable[0]);
}

0

이것은 @Kano@Mx가 제공 한 훌륭한 솔루션에 대한 잠재적 인 향상 입니다. TabIndex 순서를 유지하려면 중간에 다음 정렬을 추가하십시오.

// Sort by explicit Tab Index, if any
var sort_by_TabIndex = function (elementA, elementB) {
    let a = elementA.tabIndex || 1;
    let b = elementB.tabIndex || 1;
    if (a < b) { return -1; }
    if (a > b) { return 1; }
    return 0;
}
focussable.sort(sort_by_TabIndex);

0

이것을 호출 할 수 있습니다.

탭:

$.tabNext();

Shift + Tab :

$.tabPrev();

<!DOCTYPE html>
<html>
<body>
<script src="https://code.jquery.com/jquery-3.3.1.js" integrity="sha256-2Kok7MbOyxpgUVvAk/HJ2jigOSYS2auK4Pfzbm7uH60=" crossorigin="anonymous"></script>
<script>
(function($){
	'use strict';

	/**
	 * Focusses the next :focusable element. Elements with tabindex=-1 are focusable, but not tabable.
	 * Does not take into account that the taborder might be different as the :tabbable elements order
	 * (which happens when using tabindexes which are greater than 0).
	 */
	$.focusNext = function(){
		selectNextTabbableOrFocusable(':focusable');
	};

	/**
	 * Focusses the previous :focusable element. Elements with tabindex=-1 are focusable, but not tabable.
	 * Does not take into account that the taborder might be different as the :tabbable elements order
	 * (which happens when using tabindexes which are greater than 0).
	 */
	$.focusPrev = function(){
		selectPrevTabbableOrFocusable(':focusable');
	};

	/**
	 * Focusses the next :tabable element.
	 * Does not take into account that the taborder might be different as the :tabbable elements order
	 * (which happens when using tabindexes which are greater than 0).
	 */
	$.tabNext = function(){
		selectNextTabbableOrFocusable(':tabbable');
	};

	/**
	 * Focusses the previous :tabbable element
	 * Does not take into account that the taborder might be different as the :tabbable elements order
	 * (which happens when using tabindexes which are greater than 0).
	 */
	$.tabPrev = function(){
		selectPrevTabbableOrFocusable(':tabbable');
	};

    function tabIndexToInt(tabIndex){
        var tabIndexInded = parseInt(tabIndex);
        if(isNaN(tabIndexInded)){
            return 0;
        }else{
            return tabIndexInded;
        }
    }

    function getTabIndexList(elements){
        var list = [];
        for(var i=0; i<elements.length; i++){
            list.push(tabIndexToInt(elements.eq(i).attr("tabIndex")));
        }
        return list;
    }

    function selectNextTabbableOrFocusable(selector){
        var selectables = $(selector);
        var current = $(':focus');

        // Find same TabIndex of remainder element
        var currentIndex = selectables.index(current);
        var currentTabIndex = tabIndexToInt(current.attr("tabIndex"));
        for(var i=currentIndex+1; i<selectables.length; i++){
            if(tabIndexToInt(selectables.eq(i).attr("tabIndex")) === currentTabIndex){
                selectables.eq(i).focus();
                return;
            }
        }

        // Check is last TabIndex
        var tabIndexList = getTabIndexList(selectables).sort(function(a, b){return a-b});
        if(currentTabIndex === tabIndexList[tabIndexList.length-1]){
            currentTabIndex = -1;// Starting from 0
        }

        // Find next TabIndex of all element
        var nextTabIndex = tabIndexList.find(function(element){return currentTabIndex<element;});
        for(var i=0; i<selectables.length; i++){
            if(tabIndexToInt(selectables.eq(i).attr("tabIndex")) === nextTabIndex){
                selectables.eq(i).focus();
                return;
            }
        }
    }

	function selectPrevTabbableOrFocusable(selector){
		var selectables = $(selector);
		var current = $(':focus');

		// Find same TabIndex of remainder element
        var currentIndex = selectables.index(current);
        var currentTabIndex = tabIndexToInt(current.attr("tabIndex"));
        for(var i=currentIndex-1; 0<=i; i--){
            if(tabIndexToInt(selectables.eq(i).attr("tabIndex")) === currentTabIndex){
                selectables.eq(i).focus();
                return;
            }
        }

        // Check is last TabIndex
        var tabIndexList = getTabIndexList(selectables).sort(function(a, b){return b-a});
        if(currentTabIndex <= tabIndexList[tabIndexList.length-1]){
            currentTabIndex = tabIndexList[0]+1;// Starting from max
        }

        // Find prev TabIndex of all element
        var prevTabIndex = tabIndexList.find(function(element){return element<currentTabIndex;});
        for(var i=selectables.length-1; 0<=i; i--){
            if(tabIndexToInt(selectables.eq(i).attr("tabIndex")) === prevTabIndex){
                selectables.eq(i).focus();
                return;
            }
        }
	}

	/**
	 * :focusable and :tabbable, both taken from jQuery UI Core
	 */
	$.extend($.expr[ ':' ], {
		data: $.expr.createPseudo ?
			$.expr.createPseudo(function(dataName){
				return function(elem){
					return !!$.data(elem, dataName);
				};
			}) :
			// support: jQuery <1.8
			function(elem, i, match){
				return !!$.data(elem, match[ 3 ]);
			},

		focusable: function(element){
			return focusable(element, !isNaN($.attr(element, 'tabindex')));
		},

		tabbable: function(element){
			var tabIndex = $.attr(element, 'tabindex'),
				isTabIndexNaN = isNaN(tabIndex);
			return ( isTabIndexNaN || tabIndex >= 0 ) && focusable(element, !isTabIndexNaN);
		}
	});

	/**
	 * focussable function, taken from jQuery UI Core
	 * @param element
	 * @returns {*}
	 */
	function focusable(element){
		var map, mapName, img,
			nodeName = element.nodeName.toLowerCase(),
			isTabIndexNotNaN = !isNaN($.attr(element, 'tabindex'));
		if('area' === nodeName){
			map = element.parentNode;
			mapName = map.name;
			if(!element.href || !mapName || map.nodeName.toLowerCase() !== 'map'){
				return false;
			}
			img = $('img[usemap=#' + mapName + ']')[0];
			return !!img && visible(img);
		}
		return ( /^(input|select|textarea|button|object)$/.test(nodeName) ?
			!element.disabled :
			'a' === nodeName ?
				element.href || isTabIndexNotNaN :
				isTabIndexNotNaN) &&
			// the element and all of its ancestors must be visible
			visible(element);

		function visible(element){
			return $.expr.filters.visible(element) && !$(element).parents().addBack().filter(function(){
				return $.css(this, 'visibility') === 'hidden';
			}).length;
		}
	}
})(jQuery);
</script>

<a tabindex="5">5</a><br>
<a tabindex="20">20</a><br>
<a tabindex="3">3</a><br>
<a tabindex="7">7</a><br>
<a tabindex="20">20</a><br>
<a tabindex="0">0</a><br>

<script>
var timer;
function tab(){
    window.clearTimeout(timer)
    timer = window.setInterval(function(){$.tabNext();}, 1000);
}
function shiftTab(){
    window.clearTimeout(timer)
    timer = window.setInterval(function(){$.tabPrev();}, 1000);
}
</script>
<button tabindex="-1" onclick="tab()">Tab</button>
<button tabindex="-1" onclick="shiftTab()">Shift+Tab</button>

</body>
</html>

완료하려면 jquery.tabbable 플러그인을 수정 합니다.


해당 jQuery 플러그인의 작성자가 게시 한이 답변의 중복 .
mbomb007

0

네크 로맨싱.
키보드로 탐색하고 싶은 0-tabIndexes가 많이 있습니다.
이 경우 요소의 순서 만 중요했기 때문에document.createTreeWalker

따라서 먼저 필터를 만듭니다 (NUMERICAL 값이있는 "tabIndex"속성이있는 [표시] 요소 만 필요합니다.

그런 다음 검색하지 않으려는 루트 노드를 설정합니다. 제 경우 this.m_tree에는 전환 가능한 트리를 포함하는 ul 요소입니다. 대신 전체 문서를 원하는 경우에, 다만 교체 this.m_tree와 함께document.documentElement .

그런 다음 현재 노드를 현재 활성 요소로 설정합니다.

ni.currentNode = el; // el = document.activeElement

그런 다음 ni.nextNode()또는 ni.previousNode().

참고 :
tabIndices! = 0이 있고 요소 순서가 tabIndex 순서가 아닌 경우 탭이 올바른 순서로 반환되지 않습니다. tabIndex = 0 인 경우 tabOrder는 항상 요소 순서이므로 이것이 작동합니다 (이 경우).

protected createFilter(fn?: (node: Node) => number): NodeFilter
{
    // Accept all currently filtered elements.
    function acceptNode(node: Node): number 
    {
        return NodeFilter.FILTER_ACCEPT;
    }

    if (fn == null)
        fn = acceptNode;


    // Work around Internet Explorer wanting a function instead of an object.
    // IE also *requires* this argument where other browsers don't.
    const safeFilter: NodeFilter = <NodeFilter><any>fn;
    (<any>safeFilter).acceptNode = fn;

    return safeFilter;
}



protected createTabbingFilter(): NodeFilter
{
    // Accept all currently filtered elements.
    function acceptNode(node: Node): number 
    {
        if (!node)
            return NodeFilter.FILTER_REJECT;

        if (node.nodeType !== Node.ELEMENT_NODE)
            return NodeFilter.FILTER_REJECT;

        if (window.getComputedStyle(<Element>node).display === "none")
            return NodeFilter.FILTER_REJECT;

        // "tabIndex": "0"
        if (!(<Element>node).hasAttribute("tabIndex"))
            return NodeFilter.FILTER_SKIP;

        let tabIndex = parseInt((<Element>node).getAttribute("tabIndex"), 10);
        if (!tabIndex || isNaN(tabIndex) || !isFinite(tabIndex))
            return NodeFilter.FILTER_SKIP;

        // if ((<Element>node).tagName !== "LI") return NodeFilter.FILTER_SKIP;

        return NodeFilter.FILTER_ACCEPT;
    }

    return this.createFilter(acceptNode);
}


protected getNextTab(el: HTMLElement): HTMLElement
{
    let currentNode: Node;
    // https://developer.mozilla.org/en-US/docs/Web/API/Document/createNodeIterator
    // https://developer.mozilla.org/en-US/docs/Web/API/Document/createTreeWalker

    // let ni = document.createNodeIterator(el, NodeFilter.SHOW_ELEMENT);
    // let ni = document.createTreeWalker(this.m_tree, NodeFilter.SHOW_ELEMENT);
    let ni = document.createTreeWalker(this.m_tree, NodeFilter.SHOW_ELEMENT, this.createTabbingFilter(), false);

    ni.currentNode = el;

    while (currentNode = ni.nextNode())
    {
        return <HTMLElement>currentNode;
    }

    return el;
}


protected getPreviousTab(el: HTMLElement): HTMLElement
{
    let currentNode: Node;
    let ni = document.createTreeWalker(this.m_tree, NodeFilter.SHOW_ELEMENT, this.createTabbingFilter(), false);
    ni.currentNode = el;

    while (currentNode = ni.previousNode())
    {
        return <HTMLElement>currentNode;
    }

    return el;
}

while 루프는

while (currentNode = ni.nextNode())
{
    // Additional checks here
    // if(condition) return currentNode;
    // else the loop continues;
    return <HTMLElement>currentNode; // everything is already filtered down to what we need here
}

createTreeWalker에 전달 된 필터에서 필터링 할 수없는 추가 기준이있는 경우에만 원하는 경우에만 있습니다.

이것은 TypeScript입니다. 콜론 (:) 뒤와 꺾쇠 괄호 (<>) 사이의 모든 토큰을 제거해야합니다 (예 : <Element>또는:(node: Node) => number 유효한 자바 스크립트를 얻을 수 있습니다.

여기 서비스로 트랜스 파일 된 JS :

"use strict";
function createFilter(fn) {
    // Accept all currently filtered elements.
    function acceptNode(node) {
        return NodeFilter.FILTER_ACCEPT;
    }
    if (fn == null)
        fn = acceptNode;
    // Work around Internet Explorer wanting a function instead of an object.
    // IE also *requires* this argument where other browsers don't.
    const safeFilter = fn;
    safeFilter.acceptNode = fn;
    return safeFilter;
}
function createTabbingFilter() {
    // Accept all currently filtered elements.
    function acceptNode(node) {
        if (!node)
            return NodeFilter.FILTER_REJECT;
        if (node.nodeType !== Node.ELEMENT_NODE)
            return NodeFilter.FILTER_REJECT;
        if (window.getComputedStyle(node).display === "none")
            return NodeFilter.FILTER_REJECT;
        // "tabIndex": "0"
        if (!node.hasAttribute("tabIndex"))
            return NodeFilter.FILTER_SKIP;
        let tabIndex = parseInt(node.getAttribute("tabIndex"), 10);
        if (!tabIndex || isNaN(tabIndex) || !isFinite(tabIndex))
            return NodeFilter.FILTER_SKIP;
        // if ((<Element>node).tagName !== "LI") return NodeFilter.FILTER_SKIP;
        return NodeFilter.FILTER_ACCEPT;
    }
    return createFilter(acceptNode);
}
function getNextTab(el) {
    let currentNode;
    // https://developer.mozilla.org/en-US/docs/Web/API/Document/createNodeIterator
    // https://developer.mozilla.org/en-US/docs/Web/API/Document/createTreeWalker
    // let ni = document.createNodeIterator(el, NodeFilter.SHOW_ELEMENT);
    // let ni = document.createTreeWalker(this.m_tree, NodeFilter.SHOW_ELEMENT);
    let ni = document.createTreeWalker(document.documentElement, NodeFilter.SHOW_ELEMENT, createTabbingFilter(), false);
    ni.currentNode = el;
    while (currentNode = ni.nextNode()) {
        return currentNode;
    }
    return el;
}
function getPreviousTab(el) {
    let currentNode;
    let ni = document.createTreeWalker(document.documentElement, NodeFilter.SHOW_ELEMENT, createTabbingFilter(), false);
    ni.currentNode = el;
    while (currentNode = ni.previousNode()) {
        return currentNode;
    }
    return el;
}

-1

순환하려는 각 요소에 대해 고유 한 tabIndex 값을 지정 했습니까? 그렇다면 다음을 시도해 볼 수 있습니다.

var lasTabIndex = 10; //Set this to the highest tabIndex you have
function OnFocusOut()
{
    var currentElement = $get(currentElementId); // ID set by OnFocusIn 

    var curIndex = $(currentElement).attr('tabindex'); //get the tab index of the current element
    if(curIndex == lastTabIndex) { //if we are on the last tabindex, go back to the beginning
        curIndex = 0;
    }
    $('[tabindex=' + (curIndex + 1) + ']').focus(); //set focus on the element that has a tab index one greater than the current tab index
}

jquery를 사용하고 있습니까?


우리는 JQuery를 사용하지 않습니다. : /
JadziaMD 2011-08-26

좋아, 내가 jquery를 사용하지 않고 다시 작성할 수 있다고 생각합니다.
잠시만 기다려

관심있는 모든 요소에는 탭 인덱스 값이 설정되어 있습니다.
JadziaMD

-1

위의 솔루션을 확인한 결과 상당히 길었습니다. 한 줄의 코드로 수행 할 수 있습니다.

currentElement.nextElementSibling.focus();

또는

currentElement.previousElementSibling.focus();

여기서 currentElement는 임의의 즉 document.activeElement이거나 현재 요소가 함수의 컨텍스트에있는 경우 this 일 수 있습니다.

keydown 이벤트로 탭 및 시프트 탭 이벤트를 추적했습니다.

let cursorDirection = ''
$(document).keydown(function (e) {
    let key = e.which || e.keyCode;
    if (e.shiftKey) {
        //does not matter if user has pressed tab key or not.
        //If it matters for you then compare it with 9
        cursorDirection = 'prev';
    }
    else if (key == 9) {
        //if tab key is pressed then move next.
        cursorDirection = 'next';
    }
    else {
        cursorDirection == '';
    }
});

커서 방향이 있으면 nextElementSibling.focus또는 previousElementSibling.focus방법을 사용할 수 있습니다.


1
불행히도 형제 순서는 우연의 일치를 제외하고는 탭 순서와 관련이 없으며 이전 / 다음 형제가 초점을 맞출 수 있다는 보장도 없습니다.
Lawrence Dol
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.