Tiêu điểm yếu tố tiếp theo trong chỉ mục tab


103

Tôi đang cố gắng di chuyển tiêu điểm đến phần tử tiếp theo trong chuỗi tab dựa trên phần tử hiện tại có tiêu điểm. Cho đến nay tôi đã không tìm thấy bất cứ điều gì trong các tìm kiếm của tôi.

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

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

Tất nhiên, nextElementByTabIndex là phần quan trọng để điều này hoạt động. Làm cách nào để tìm phần tử tiếp theo trong chuỗi tab? Giải pháp sẽ cần dựa trên JScript chứ không phải JQuery.


3
tại sao bạn có dòng này currentElementId = "";?

1
Tôi không nghĩ rằng bất kỳ trình duyệt nào cũng tiết lộ thông tin thứ tự tab - và thuật toán do chính trình duyệt sử dụng quá phức tạp để sao chép. Có lẽ bạn có thể hạn chế yêu cầu của bạn, ví dụ: "chỉ xem xét input, buttontextareathẻ và bỏ qua tabindexthuộc tính".
Wladimir Palant

Chúng tôi cần xem .newElementByTabIndexmã của bạn vì mã đó không hoạt động.
0x499602D2

2
Sau đó, một lần nữa, có thể hạn chế đối với các thẻ cụ thể là không cần thiết - người ta có thể kiểm tra xem focus()phương pháp có tồn tại hay không.
Wladimir Palant

1
@David Đó là chức năng không tồn tại, do đó câu hỏi của tôi. : D
JadziaMD

Câu trả lời:


23

Không có jquery: Trước hết, trên các phần tử có thể tab của bạn, thêm class="tabable"điều này sẽ cho phép chúng tôi chọn chúng sau. (Đừng quên tiền tố bộ chọn lớp "." Trong mã bên dưới)

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
Một giải pháp mà không cần phải thêm tên cho mọi phần tử (vì có nhiều cách để thực hiện nếu khả thi) sẽ là lý tưởng.
JadziaMD

3
được rồi, đây có phải là biểu mẫu không? Nếu tất cả các yếu tố mà bạn muốn là những yếu tố đầu vào, bạn có thể thay thế dòng var tabbables = document.getElementsByName("tabable");với var tabbables = document.getElementsByTagName("input");thay
Brian Glaz

var tabbables = document.querySelectorAll("input, textarea, button")// IE8 +, nhận tham chiếu đến tất cả các bảng tab mà không cần sửa đổi HTML của bạn.
Greg

2
class = "tabbable" hơn là sử dụng các tên thuộc tính
Chris F Carroll

4
Lưu ý rằng sử dụng flexbox, thứ tự của các phần tử trong DOM khác với trực quan trong trình duyệt. Chỉ chọn phần tử có thể tab tiếp theo không hoạt động khi bạn thay đổi thứ tự của các phần tử bằng cách sử dụng flexbox.
Haneev

75

Tôi chưa bao giờ thực hiện điều này, nhưng tôi đã xem xét một vấn đề tương tự và đây là những gì tôi sẽ thử.

Hãy thử cái này trước

Đầu tiên, tôi sẽ xem liệu bạn có thể chỉ cần kích hoạt một keypresssự kiện cho phím Tab trên phần tử hiện có tiêu điểm hay không. Có thể có một cách khác để thực hiện việc này cho các trình duyệt khác nhau.

Nếu điều đó không hiệu quả, bạn sẽ phải làm việc chăm chỉ hơn…

Tham chiếu đến việc triển khai jQuery, bạn phải:

  1. Nghe Tab và Shift + Tab
  2. Biết phần tử nào có thể tab
  3. Hiểu cách thức hoạt động của thứ tự tab

1. Nghe Tab và Shift + Tab

Nghe Tab và Shift + Tab có lẽ đã được đề cập kỹ ở những nơi khác trên web, vì vậy tôi sẽ bỏ qua phần đó.

2. Biết phần tử nào có thể tab

Biết được phần tử nào có thể tab sẽ khó hơn. Về cơ bản, một phần tử có thể ở dạng tab nếu nó có thể lấy tiêu điểm và không có bộ thuộc tính tabindex="-1". Vì vậy, sau đó chúng ta phải hỏi những yếu tố nào có thể lấy nét được. Các yếu tố sau có thể lấy tiêu điểm:

  • input, select, textarea, button, Và objectcác yếu tố mà không bị khuyết tật.
  • aareacác phần tử có một hrefhoặc có một giá trị số cho tabindextập hợp.
  • bất kỳ phần tử nào có giá trị số cho tabindextập hợp.

Hơn nữa, một phần tử chỉ có thể lấy tiêu điểm nếu:

  • Không có tổ tiên nào của nó display: none.
  • Giá trị được tính của visibilityvisible. Điều này có nghĩa là tổ tiên gần nhất visibilityđược đặt phải có giá trị là visible. Nếu không có tổ tiên nào visibilityđược đặt, thì giá trị được tính là visible.

Thêm chi tiết có trong một câu trả lời Stack Overflow khác .

3. Hiểu cách thức hoạt động của thứ tự tab

Thứ tự tab của các phần tử trong tài liệu được kiểm soát bởi tabindexthuộc tính. Nếu không có giá trị nào được đặt, giá trị tabindexlà hiệu quả 0.

Thứ tabindextự cho tài liệu là: 1, 2, 3,…, 0.

Ban đầu, khi bodyphần tử (hoặc không có phần tử) có tiêu điểm, phần tử đầu tiên trong thứ tự tab là phần tử khác 0 thấp nhất tabindex. Nếu nhiều phần tử giống nhau tabindex, thì bạn đi theo thứ tự tài liệu cho đến khi bạn đến phần tử cuối cùng với phần tử đó tabindex. Sau đó, bạn di chuyển đến mức thấp nhất tiếp theo tabindexvà quá trình tiếp tục. Cuối cùng, kết thúc với các phần tử đó bằng số 0 (hoặc trống) tabindex.


37

Đây là thứ tôi xây dựng cho mục đích này:

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();
        }                    
    }
}

Đặc trưng:

  • tập hợp các phần tử có thể lấy tiêu điểm có thể định cấu hình
  • không cần jQuery
  • hoạt động trong tất cả các trình duyệt hiện đại
  • nhanh và nhẹ

2
Đây là giải pháp hiệu quả nhất và thân thiện với tài nguyên. Cảm ơn bạn! Đây là kịch bản làm việc đầy đủ của tôi: stackoverflow.com/a/40686327/1589669
eapo

Tôi đã thêm đoạn mã bên dưới để bao gồm sắp xếp theo TabIndex focussable.sort (sort_by_TabIndex)
DavB.cs Ngày

1
Tốt nhất ! Nó phải phức tạp đến mức: cái nextElementSiblingcó thể không tiêu điểm được, cái có thể lấy tiêu điểm tiếp theo có thể không phải là anh chị em.
Tinmarino

Cách tiếp cận tốt, nhưng nó phải cho phép bất kỳ đầu vào nào không thuộc loại hiddenvà cũng bao gồm textareaselect.
Lucero

23

Tôi đã tạo một plugin jQuery đơn giản thực hiện điều này. Nó sử dụng bộ chọn ': tabbable' của giao diện người dùng jQuery để tìm phần tử 'tabbable' tiếp theo và chọn nó.

Ví dụ sử dụng:

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

8

Cốt lõi của câu trả lời nằm ở việc tìm ra yếu tố tiếp theo:

  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];
  }

Sử dụng:

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

Lưu ý rằng tôi không quan tâm đến việc sắp xếp thứ tự ưu tiên tabIndex.


3
Điều gì sẽ xảy ra nếu lệnh tabindex đi ngược lại với lệnh chứng từ? Tôi nghĩ rằng mảng phải được sắp xếp theo số tabIndex sau đó theo lệnh tài liệu
Chris F Carroll

Đúng, đó sẽ là "tuân thủ kỹ thuật" hơn. Tôi không chắc chắn về cạnh trường hợp, liên quan đến các yếu tố cha mẹ, vv
André Werlang

Điều gì sẽ xảy ra nếu một mục không phải là một trong những thẻ đó có thuộc tính tabindex?
Matt Pennington

1
@MattPennington Nó sẽ bị bỏ qua. Bộ lọc là (một nỗ lực) để tăng tốc độ tìm kiếm, hãy thoải mái thích nghi.
André Werlang

3

Như đã đề cập trong nhận xét ở trên, tôi không nghĩ rằng bất kỳ trình duyệt nào cũng tiết lộ thông tin thứ tự tab. Dưới đây là một ước lượng gần đúng đơn giản về những gì trình duyệt làm để lấy phần tử tiếp theo theo thứ tự tab:

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();

Điều này chỉ xem xét một số thẻ và bỏ qua tabindexthuộc tính nhưng có thể đủ tùy thuộc vào những gì bạn đang cố gắng đạt được.


3

Dường như bạn có thể kiểm tra thuộc tabIndextính của một phần tử để xác định xem nó có thể lấy tiêu điểm hay không. Phần tử không thể lấy tiêu điểm có tabindex"-1".

Sau đó, bạn chỉ cần biết các quy tắc cho các điểm dừng tab:

  • tabIndex="1" có giá trị cao nhất.
  • tabIndex="2" có mức độ ưu tiên cao nhất tiếp theo.
  • tabIndex="3" là tiếp theo, và như vậy.
  • tabIndex="0" (hoặc tabbable theo mặc định) có mức độ ưu tiên thấp nhất.
  • tabIndex="-1" (hoặc không phải tab theo mặc định) không hoạt động như một điểm dừng tab.
  • Đối với hai phần tử có cùng tabIndex, phần tử xuất hiện đầu tiên trong DOM có mức độ ưu tiên cao hơn.

Đây là một ví dụ về cách tạo danh sách các điểm dừng tab, theo trình tự, sử dụng Javascript thuần túy:

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]);
}

Trước tiên, chúng tôi đi bộ DOM, thu thập tất cả các điểm dừng tab theo trình tự với chỉ mục của chúng. Sau đó chúng tôi tập hợp danh sách cuối cùng. Lưu ý rằng chúng tôi thêm các mục có tabIndex="0"ở cuối danh sách, sau các mục có tabIndexsố 1, 2, 3, v.v.

Đối với một ví dụ hoàn toàn làm việc, nơi bạn có thể tab xung quanh bằng cách sử dụng "enter", hãy khám phá fiddle .


2

Tabbable là một gói JS nhỏ cung cấp cho bạn danh sách tất cả các phần tử có thể lập tab theo thứ tự tab . Vì vậy, bạn có thể tìm thấy phần tử của mình trong danh sách đó, sau đó tập trung vào mục nhập danh sách tiếp theo.

Gói xử lý chính xác các trường hợp cạnh phức tạp được đề cập trong các câu trả lời khác (ví dụ: không thể có tổ tiên display: none). Và nó không phụ thuộc vào jQuery!

Kể từ bản viết này (phiên bản 1.1.1), nó có những cảnh báo rằng nó không hỗ trợ IE8 và các lỗi của trình duyệt khiến nó không thể xử lý contenteditablechính xác.


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

Đây là bài đăng đầu tiên của tôi trên SO, vì vậy tôi không có đủ danh tiếng để bình luận câu trả lời được chấp nhận, nhưng tôi đã phải sửa đổi mã thành như sau:

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()
      }                    
  }
}

Việc thay đổi var thành hằng số là không quan trọng. Thay đổi chính là chúng tôi loại bỏ bộ chọn kiểm tra tabindex! = "-1". Sau đó, nếu phần tử có thuộc tính tabindex VÀ nó được đặt thành "-1", chúng tôi KHÔNG coi nó là có thể lấy tiêu điểm.

Lý do tôi cần thay đổi điều này là vì khi thêm tabindex = "- 1" vào an <input>, phần tử này vẫn được coi là có thể lấy tiêu điểm vì nó khớp với bộ chọn "input [type = text]: not ([disable])". Thay đổi của tôi tương đương với "nếu chúng tôi là đầu vào văn bản không bị vô hiệu hóa và chúng tôi có thuộc tính tabIndex và giá trị của thuộc tính đó là -1, thì chúng tôi sẽ không được coi là có thể lấy tiêu điểm.

Tôi tin rằng khi tác giả của câu trả lời được chấp nhận chỉnh sửa câu trả lời của họ để giải thích cho thuộc tính tabIndex, họ đã làm như vậy không chính xác. Vui lòng cho tôi biết nếu đây không phải là trường hợp


1

Có thuộc tính tabindex có thể được đặt trên thành phần. Nó chỉ định thứ tự các thành phần đầu vào nên được lặp lại khi chọn một và nhấn tab. Các giá trị trên 0 được dành riêng cho điều hướng tùy chỉnh, 0 là "theo thứ tự tự nhiên" (vì vậy sẽ hoạt động khác nếu được đặt cho phần tử đầu tiên), -1 có nghĩa là bàn phím không thể đặt tiêu điểm:

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

Nó cũng có thể được đặt cho một cái gì đó khác ngoài các trường nhập văn bản nhưng không rõ ràng nó sẽ làm gì ở đó, nếu có gì cả. Ngay cả khi điều hướng hoạt động, có lẽ tốt hơn nên sử dụng "trật tự tự nhiên" cho bất kỳ điều gì khác ngoài các yếu tố người dùng nhập rất rõ ràng.

Không, bạn không cần JQuery hoặc bất kỳ tập lệnh nào để hỗ trợ đường dẫn điều hướng tùy chỉnh này. Bạn có thể triển khai nó ở phía máy chủ mà không cần hỗ trợ JavaScript. Từ phía bên kia, thuộc tính cũng hoạt động tốt trong khuôn khổ React nhưng không yêu cầu nó.


0

Đây là một phiên bản đầy đủ hơn của việc tập trung vào yếu tố tiếp theo. Nó tuân theo các nguyên tắc đặc tả và sắp xếp danh sách các phần tử một cách chính xác bằng cách sử dụng tabindex. Ngoài ra, một biến ngược được xác định nếu bạn muốn lấy phần tử trước đó.

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

Đây là một cải tiến tiềm năng cho giải pháp tuyệt vời mà @Kano@Mx đã cung cấp. Nếu bạn muốn duy trì thứ tự TabIndex, hãy thêm loại này vào giữa:

// 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

Bạn có thể gọi đây là:

Chuyển hướng:

$.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>

Tôi sửa đổi jquery.tabbable PlugIn để hoàn thành.


Bản sao của câu trả lời này , được đăng bởi người tạo plugin jQuery đó.
mbomb007

0

Thăng bằng.
Tôi có một loạt 0-tabIndexes, tôi muốn điều hướng bằng bàn phím.
Vì trong trường hợp đó, chỉ có THỨ TỰ của các phần tử là quan trọng, tôi đã thực hiện bằng cách sử dụngdocument.createTreeWalker

Vì vậy, trước tiên, bạn tạo bộ lọc (bạn chỉ muốn các phần tử [hiển thị], có thuộc tính "tabIndex" với giá trị NUMERICAL.

Sau đó, bạn đặt nút gốc mà bạn không muốn tìm kiếm. Trong trường hợp của tôi, this.m_treelà một phần tử ul chứa một cây có thể chuyển đổi. Nếu bạn muốn toàn bộ tài liệu thay thế, chỉ cần thay thế this.m_treebằng document.documentElement.

Sau đó, bạn đặt nút hiện tại thành phần tử hoạt động hiện tại:

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

Sau đó, bạn trở lại ni.nextNode()hoặc ni.previousNode().

Lưu ý:
điều này sẽ KHÔNG trả về các tab theo đúng thứ tự nếu bạn có tabIndices! = 0 và thứ tự phần tử KHÔNG phải là thứ tự tabIndex. Trong trường hợp tabIndex = 0, tabOrder luôn là thứ tự phần tử, đó là lý do tại sao điều này hoạt động (trong trường hợp đó).

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;
}

Lưu ý rằng vòng lặp 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
}

chỉ ở đó nếu bạn muốn nếu bạn có các tiêu chí bổ sung mà bạn không thể lọc trong bộ lọc được chuyển đến createTreeWalker.

Lưu ý rằng đây là TypeScript, bạn cần xóa tất cả các mã thông báo đằng sau dấu hai chấm (:) và giữa các dấu ngoặc nhọn (<>), ví dụ: <Element>hoặc :(node: Node) => numberđể có được JavaScript hợp lệ.

Đây là một dịch vụ, JS được chuyển đổi:

"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

Bạn có chỉ định các giá trị tabIndex của riêng mình cho từng phần tử bạn muốn chuyển qua không? nếu vậy, bạn có thể thử điều này:

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
}

Bạn đang sử dụng jquery, phải không?


Chúng tôi không sử dụng JQuery vì nó phá vỡ ứng dụng. : /
JadziaMD

Ok, tôi nghĩ tôi có thể viết lại được mà không sử dụng jquery, cho tôi một phút
Brian Glaz

Mỗi phần tử chúng tôi quan tâm đều có đặt giá trị chỉ mục tab của chúng.
JadziaMD

-1

Tôi đã kiểm tra các giải pháp trên và thấy chúng khá dài. Nó có thể được thực hiện chỉ với một dòng mã:

currentElement.nextElementSibling.focus();

hoặc là

currentElement.previousElementSibling.focus();

ở đây currentElement có thể là bất kỳ tức là document.activeElement hoặc điều này nếu phần tử hiện tại nằm trong ngữ cảnh của hàm.

Tôi đã theo dõi các sự kiện tab và shift-tab với sự kiện 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 == '';
    }
});

khi bạn có hướng con trỏ thì bạn có thể sử dụng nextElementSibling.focushoặc previousElementSibling.focuscác phương pháp


1
Thật không may, thứ tự anh chị em không liên quan đến thứ tự tab, ngoại trừ sự trùng hợp ngẫu nhiên và không có gì đảm bảo anh chị em trước / sau thậm chí sẽ có thể lấy tiêu điểm.
Lawrence Dol
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.