Фокус следующий элемент в указателе вкладок
Я пытаюсь переместить фокус на следующий элемент в последовательности вкладок на основе текущего элемента, который имеет фокус. Пока что я ничего не нашел в моих поисках.
function OnFocusOut()
{
var currentElement = $get(currentElementId); // ID set by OnFocusIn
currentElementId = "";
currentElement.nextElementByTabIndex.focus();
}
Конечно, nextElementByTabIndex является ключевой частью для этой работы. Как найти следующий элемент в последовательности вкладок? Решение должно быть основано на JScript, а не на JQuery.
25 ответов
Без 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;
}
}
}
Я никогда не реализовывал это, но я смотрел на подобную проблему, и вот что я попробую.
Попробуйте сначала
Во-первых, я бы посмотрел, можно ли keypress
событие для клавиши Tab на элементе, который в данный момент имеет фокус. Для разных браузеров это может быть по-разному.
Если это не сработает, вам придется работать усерднее...
Обращаясь к реализации jQuery, вы должны:
- Слушайте Tab и Shift+Tab
- Знать, какие элементы имеют вкладки
- Понять, как работает порядок вкладок
1. Прослушайте Tab и Shift+Tab
Прослушивание Tab и Shift+Tab, вероятно, хорошо освещено в Интернете, поэтому я пропущу эту часть.
2. Знать, какие элементы имеют вкладки
Знать, какие элементы доступны для вкладок, сложнее. По сути, элемент доступен для табуляции, если он фокусируемый и не имеет атрибута tabindex="-1"
задавать. Итак, мы должны спросить, какие элементы являются фокусируемыми. Следующие элементы являются фокусируемыми:
input
,select
,textarea
,button
, а такжеobject
элементы, которые не отключены.a
а такжеarea
элементы, которые имеютhref
или иметь числовое значение дляtabindex
задавать.- любой элемент, который имеет числовое значение для
tabindex
задавать.
Кроме того, элемент является фокусируемым, только если:
- Ни один из его предков не
display: none
, - Расчетное значение
visibility
являетсяvisible
, Это означает, что ближайший предокvisibility
набор должен иметь значениеvisible
, Если нет предкаvisibility
установить, то вычисленное значениеvisible
,
Более подробная информация в другом ответе переполнения стека.
3. Понять, как работает порядок вкладок
Порядок табуляции элементов в документе контролируется tabindex
приписывать. Если значение не установлено, tabindex
эффективно 0
,
tabindex
заказ на документ: 1, 2, 3, …, 0.
Первоначально, когда body
элемент (или элемент отсутствует) имеет фокус, первый элемент в порядке табуляции является самым низким ненулевым tabindex
, Если несколько элементов имеют одинаковые tabindex
затем вы идете в порядке документов, пока не достигнете последнего элемента с этим tabindex
, Затем вы переходите к следующему нижнему tabindex
и процесс продолжается. Наконец, закончите с этими элементами с нуля (или пустым) tabindex
,
Вот что я создаю для этой цели:
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 не требуется
- работает во всех современных браузерах
- быстрый и легкий
Я создал простой плагин jQuery, который делает именно это. Он использует селектор ":tabbable" пользовательского интерфейса jQuery, чтобы найти следующий элемент "tabbable" и выбрать его.
Пример использования:
// Simulate tab key when element is clicked
$('.myElement').bind('click', function(event){
$.tabNext();
return false;
});
Суть ответа лежит в поиске следующего элемента:
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
,
Кажется, что вы можете проверить tabIndex
свойство элемента, чтобы определить, является ли он фокусируемым. Элемент, который не фокусируется, имеет tabindex
из "-1".
Тогда вам просто нужно знать правила для табуляции:
tabIndex="1"
имеет самые высокие приоритеты.tabIndex="2"
имеет следующий наивысший приоритет.tabIndex="3"
рядом и так далее.tabIndex="0"
(или вкладка по умолчанию) имеет самый низкий приоритет.tabIndex="-1"
(или не вкладка по умолчанию) не действует как табуляция.- Для двух элементов, имеющих одинаковый tabIndex, тот, который появляется первым в DOM, имеет более высокий приоритет.
Вот пример того, как построить список позиций табуляции в последовательности, используя чистый Javascript:
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 и т. д.
Для полностью работающего примера, где вы можете перемещаться по клавише "ввод", посмотрите эту скрипку.
Tabbable - это небольшой пакет JS, который предоставляет вам список всех элементов tabbable в порядке табуляции. Таким образом, вы можете найти свой элемент в этом списке, а затем сосредоточиться на следующей записи списка.
Пакет правильно обрабатывает сложные крайние случаи, упомянутые в других ответах (например, предок не может быть display: none
). И это не зависит от jQuery!
На момент написания этой статьи (версия 1.1.1) у него были оговорки, что он не поддерживает IE8, и что ошибки браузера мешают его обработке contenteditable
правильно.
Как упоминалось в комментарии выше, я не думаю, что какие-либо браузеры предоставляют информацию о порядке вкладок. Вот упрощенное приближение того, что делает браузер, чтобы получить следующий элемент в порядке табуляции:
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
атрибут, но может быть достаточно в зависимости от того, что вы пытаетесь достичь.
Существует свойство tabindex, которое можно установить для компонента. Он определяет, в каком порядке входные компоненты должны повторяться при выборе одного и нажатии табуляции. Значения выше 0 зарезервированы для настраиваемой навигации, 0 - "в естественном порядке" (поэтому будет вести себя по-другому, если установлено для первого элемента), -1 означает, что клавиатура не фокусируется:
<!-- navigate with tab key: -->
<input tabindex="1" type="text"/>
<input tabindex="2" type="text"/>
Его также можно установить для чего-то еще, кроме полей ввода текста, но не очень очевидно, что он там будет делать, если вообще что-нибудь. Даже если навигация работает, возможно, лучше использовать "естественный порядок" для чего-либо еще, чем очень очевидные элементы пользовательского ввода.
Нет, вам не нужен JQuery или какие-либо сценарии для поддержки этого настраиваемого пути навигации. Вы можете реализовать его на стороне сервера без поддержки JavaScript. С другой стороны, свойство также отлично работает в среде React, но не требует этого.
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();
};
Вот более полная версия сосредоточения внимания на следующем элементе. Он следует указаниям спецификации и правильно сортирует список элементов с помощью 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]);
}
Это мой первый пост на 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()
}
}
}
Изменение переменной в постоянную не является критичным. Основное изменение состоит в том, что мы избавляемся от селектора, который проверяет tabindex!= "-1". Затем, если элемент имеет атрибут tabindex И он установлен в "-1", мы НЕ считаем его фокусируемым.
Причина, по которой мне нужно было это изменить, заключалась в том, что при добавлении tabindex="-1" к <input>
этот элемент все еще считался фокусируемым, потому что он соответствует селектору "input[type=text]:not([disabled])". Мое изменение эквивалентно "если мы не отключили ввод текста, и у нас есть атрибут tabIndex, и значение этого атрибута равно -1, то мы не должны считаться фокусируемыми.
Я считаю, что когда автор принятого ответа отредактировал свой ответ для учета атрибута tabIndex, он сделал это неправильно. Пожалуйста, дайте мне знать, если это не так
Это потенциальное усовершенствование отличного решения, предлагаемого @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);
Вот мое рабочее решение с использованиемtabbable
библиотека в моем приложении React, чтобы «Ввод» вел себя как вкладка
handleKeyDown={(e) => {
if (e.key === 'Enter') {
const fElts = tabbable(document.documentElement);
const currEltIndex = fElts.findIndex(
(e) => e === document.activeElement,
);
const nextElt = fElts[currEltIndex + (e.shiftKey ? -1 : 1)];
nextElt?.focus();
}
}}
Некромантинг.
У меня есть набор индексов 0-tabIndexes, по которым я хотел перемещаться с помощью клавиатуры.
Поскольку в этом случае имел значение только ПОРЯДОК элементов, я сделал это с помощьюdocument.createTreeWalker
Итак, сначала вы создаете фильтр (вам нужны только [видимые] элементы, у которых есть атрибут 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
чтобы получить действующий JavaScript.
Вот как услуга переданный 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;
}
Обратитесь к этой чистой js-библиотеке npm для получения таких стратегий навигации по вкладкам.
keyboard-navigator
эта небольшая библиотека обрабатывает клавиши табуляции, навигацию по клавишам со стрелками, сохраняет фокус на обновлениях dom, модальную ловушку фокуса, резервный фокус.
function focusNext() {
var query = '[tabindex]';
if (document.activeElement) {
var elements = [...document.querySelectorAll(query)]
var index = elements.indexOf(document.activeElement);
index++
if (index == elements.length) index = 0
elements[index].focus()
}
}
Если вы хотите настроить таргетинг на другие элементы, вы можете изменить
query
для уточнения элементов.
Надеюсь, это полезно.
<input size="2" tabindex="1" id="one"
maxlength="2" onkeyup="toUnicode(this)" />
<input size="2" tabindex="2" id="two"
maxlength="2" onkeyup="toUnicode(this)" />
<input size="2" tabindex="3" id="three"
maxlength="2" onkeyup="toUnicode(this)" />
тогда используйте простой JavaScript
function toUnicode(elmnt)
{
var next;
if (elmnt.value.length==elmnt.maxLength)
{
next=elmnt.tabIndex + 1;
//look for the fields with the next tabIndex
var f = elmnt.form;
for (var i = 0; i < f.elements.length; i++)
{
if (next<=f.elements[i].tabIndex)
{
f.elements[i].focus();
break;
}
}
}
}
Вам просто нужно изменить индекс вкладки. Глобальный атрибут tabindex указывает, что его элементы могут быть сфокусированы, и где он участвует в последовательной навигации с клавиатуры (обычно с помощью клавиши Tab, отсюда и название).
Вы можете назвать это:
Tab:
$.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 PlugIn для завершения.
Небольшой совет: не пытайтесь контролировать, где находится фокус во время события табуляции. Вместо этого попробуйте контролировать, какие элементы можно вкладывать, а какие нет, установив параметр
tabIndex
элементов, на которые вы не хотите фокусироваться
-1
. Например
// `tabContainer` is a container where we want only
// element at a time to be tabbable, e.g. a radio menu.
tabContainer.addEventListener("focusin", () => {
const desired = findDesiredFocusElement();
if (!desired) {
// Just leave the focus be. We have no preference
// at the moment.
return;
}
// Move the focus to the correct element.
desired.focus();
// Remove all undesired elements from the tab order.
for (const undesired of findUndesiredFocusElements()) {
// Make it untabbable.
undesired.tabIndex = -1;
}
});
tabContainer.addEventListener("focusout", (event) => {
for (const element of findRelevantFocusElements()) {
// Give each element back their focus capability.
element.tabIndex = 0;
}
});
Примечание. Возможно, это не лучший вариант в вашей ситуации, например, в вашем случае лучше контролировать индекс вкладок в некоторых
change
события или не сбрасывать
tabIndex
состояние на
focusout
и т.п.
Больше информации здесь.
Вы указали свои собственные значения 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, верно?
Я использую этот код
$(document).on('change', 'select', function () {
let next_select = $(this);
// console.log(next_select.toArray())
if (!next_select.parent().parent().next().find('select').length) {
next_select.parent().parent().parent().next().find('input[type="text"]').click()
console.log(next_select.parent().parent().parent().next());
} else if (next_select.parent().parent().next().find('select').prop("disabled")) {
setTimeout(function () {
next_select.parent().parent().next().find('select').select2('open')
}, 1000)
console.log('b');
} else if (next_select.parent().parent().next().find('select').length) {
next_select.parent().parent().next().find('select').select2('open')
console.log('c');
}
});
Я проверил вышеупомянутые решения и нашел их довольно длинными. Это можно сделать всего одной строкой кода:
currentElement.nextElementSibling.focus();
или же
currentElement.previousElementSibling.focus();
здесь currentElement может быть любым, т.е. document.activeElement, или this, если текущий элемент находится в контексте функции.
Я отслеживал вкладки и события Shift-Tab с событием 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