Краткий ответ
Предполагая, что у вас нет положительных tabindex
s, тогда вышеизложенное будет работать. Однако, если у вас есть набор tabindex
(чего на самом деле не должно быть), то это немного сложнее.
Другое дело, что использование <details>
и <summary>
сделает ваше приложение более доступным.
Быстро в сторону
Как упоминал @CBroe в комментариях, использование transitionend
было бы лучше, чем setTimeout
. Я жил в каменном веке, думая, что у него нет хорошей поддержки, но всегда искал не тот товар на caniuse.com.
Длинный ответ
Сначала давайте получим соответствующий HTML, так как он дает нам некоторые мощные функции в современных браузерах.
Детали и резюме
<details>
и <summary>
автоматически предоставляют множество возможностей. Они автоматически связывают элементы управления (эквивалент aria-controls
), это чистая разметка, они автоматически имеют функции открытия и закрытия в большинстве браузеров в качестве запасного варианта на случай сбоя вашего JavaScript и т. д.
Я рассматривал их ранее, поэтому вы можете узнать больше о <details>
и <summary>
в этом ответе, который я дал.
<details>
<summary>Item 1</summary>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Ipsum, reiciendis!</p>
</details>
<details>
<summary>Item 2</summary>
<p>Lorem ipsum dolor sit amet consectetur, adipisicing elit. Ipsum, reiciendis!</p>
</details>
Обработка фокуса
Самый простой способ сделать это — использовать JavaScript для изменения свойства отображения после завершения анимации (и отобразить его до ее запуска) с помощью display: none
.
Итак, если ваша анимация составляет 1 секунду, вы просто устанавливаете display: block
перед добавлением любого класса, который запускает анимацию высоты.
Чтобы закрыть, вы запускаете анимацию высоты (удаляя свой класс) и используете setTimeout
в течение 1 секунды, чтобы затем вызвать display: none
.
Очевидно, что у этого есть проблемы, так как кто-то может в конечном итоге перейти на панель аккордеона, когда она достигнет высоты 0, а затем, когда вы установите display: none
, позиция фокуса страницы будет потеряна.
Альтернативный способ - установить tabindex="-1"
, как вы предлагаете, поскольку вы можете сделать это, как только закроете аккордеон.
Приведенный ниже пример взят из этого ответа, который я дал при настройке tabindex
в анимированном разделе.
Он учитывает больше, чем вам нужно (положительные tabindex
s, отключение анимации с помощью prefers-reduced-motion
, тот факт, что content-editable
можно использовать и т. д.), но должен дать вам необходимую информацию.
Это немного грубо по краям, но должно дать вам хорошее основание для начала.
Я добавил много комментариев в код, поэтому, надеюсь, вы поймете, какие части применимы к вам, и сможете адаптировать его для использования <details>
и <summary>
для завершения своего решения.
var content = document.getElementById('contentDiv');
var btn = document.getElementById('btn_toggle');
var animationDelay = 2000;
//We should account for people with vestibular motion disorders etc. if they have indicated they prefer reduced motion. We set the animation time to 0 seconds.
var motionQuery = matchMedia('(prefers-reduced-motion)');
function handleReduceMotionChanged() {
if (motionQuery.matches) {
animationDelay = 0;
} else {
animationDelay = 2000;
}
}
motionQuery.addListener(handleReduceMotionChanged);
handleReduceMotionChanged();
//the main function for setting the tabindex to -1 for all children of a parent with given ID (and reversing the process)
function hideOrShowAllInteractiveItems(parentDivID){
//a list of selectors for all focusable elements.
var focusableItems = ['a[href]', 'area[href]', 'input:not([disabled])', 'select:not([disabled])', 'textarea:not([disabled])', 'button:not([disabled])', '[tabindex]:not([disabled])', '[contenteditable=true]:not([disabled])'];
//build a query string that targets the parent div ID and all children elements that are in our focusable items list.
var queryString = "";
for (i = 0, leni = focusableItems.length; i < leni; i++) {
queryString += "#" + parentDivID + " " + focusableItems[i] + ", ";
}
queryString = queryString.replace(/,\s*$/, "");
var focusableElements = document.querySelectorAll(queryString);
for (j = 0, lenj = focusableElements.length; j < lenj; j++) {
var el = focusableElements[j];
if(!el.hasAttribute('data-modified')){ // we use the 'data-modified' attribute to track all items that we have applied a tabindex to (as we can't use tabindex itself).
// we haven't modified this element so we grab the tabindex if it has one and store it for use later when we want to restore.
if(el.hasAttribute('tabindex')){
el.setAttribute('data-oldTabIndex', el.getAttribute('tabindex'));
}
el.setAttribute('data-modified', true);
el.setAttribute('tabindex', '-1'); // add `tabindex="-1"` to all items to remove them from the focus order.
}else{
//we have modified this item so we want to revert it back to the original state it was in.
el.removeAttribute('tabindex');
if(el.hasAttribute('data-oldtabindex')){
el.setAttribute('tabindex', el.getAttribute('data-oldtabindex'));
el.removeAttribute('data-oldtabindex');
}
el.removeAttribute('data-modified');
}
}
}
btn.addEventListener('click', function(){
contentDiv.className = contentDiv.className !== 'show' ? 'show' : 'hide';
if (contentDiv.className === 'show') {
content.setAttribute('aria-hidden', false);
setTimeout(function(){
contentDiv.style.display = 'block';
hideOrShowAllInteractiveItems('contentDiv');
},0);
}
if (contentDiv.className === 'hide') {
content.setAttribute('aria-hidden', true);
hideOrShowAllInteractiveItems('contentDiv');
setTimeout(function(){
contentDiv.style.display = 'none';
},animationDelay); //using the animation delay set based on the users preferences.
}
});
@keyframes in {
0% { transform: scale(0); opacity: 0; visibility: hidden; }
100% { transform: scale(1); opacity: 1; visibility: visible; }
}
@keyframes out {
0% { transform: scale(1); opacity: 1; visibility: visible; }
100% { transform: scale(0); opacity: 0; visibility: hidden; }
}
#contentDiv {
background: grey;
color: white;
padding: 16px;
margin-bottom: 10px;
}
#contentDiv.show {
animation: in 2s ease both;
}
#contentDiv.hide {
animation: out 2s ease both;
}
/*****We should account for people with vestibular motion disorders etc. if they have indicated they prefer reduced motion. ***/
@media (prefers-reduced-motion) {
#contentDiv.show,
#contentDiv.hide{
animation: none;
}
}
<div id="contentDiv" class="show">
<p>Some information to be hidden</p>
<input />
<button>a button</button>
<button tabindex="1">a button with a positive tabindex that needs restoring</button>
</div>
<button id="btn_toggle"> Hide Div </button>
person
Graham Ritchie
schedule
27.10.2020