[JavaScript, HTML, Usability, Accessibility] Вы не знаете как должны работать модальные окна
Автор
Сообщение
news_bot ®
Стаж: 6 лет 8 месяцев
Сообщений: 27286
Уверен, многие хоть раз создавали всплывающее модальное окно. Но задумывались ли вы об определении этого компонента? Как он должен работать?
В этом материале я постарался собрать максимально полный свод правил, рекомендаций и примеров реализации по которым модальные окна должны работать.
Я покажу, как просто создавать сложные, удобные, производительные и доступные модальные окна независимо от браузера, платформы, устройства или способа взаимодействия пользователя.
Этот список сформирован на основе спецификаций WAI-ARIA, HTML Living Standard и моего личного опыта. И хотя я буду говорить про веб, большинство правил и рекомендаций применимы для модальных окон где угодно.
Определение модального окна
Модальное окно — это окно наложенное либо на документ, либо на другие окна. При этом, любой контент под модальным окном является недоступным для взаимодействия.
Теги и атрибуты
Интерактивным элементом для открытия диалогового окна должна выступать кнопка. Не <div>, не <span> не <a>, не любой другой тег. Исключительно <button>. И касается не только диалоговых окон, <button> — самый надежный и доступный способ создавать интерактивные элементы на странице.
Простейшая реализация кнопки открывающая диалог по его id:
<button data-modal="dialogId" onclick="document.getElementById(this.dataset.modal).showModal()">
Открыть
</button>
<dialog>
Для различных диалогов, уведомлений и прочих перекрывающих документ элементов существует тег <dialog>. Его вы и должны использовать. К огромному сожалению, его поддержка не самая лучшая:
- Chromium — полная поддержка.
- Firefox — поддержка за флагом.
- Safari не поддерживает вовсе.
Так что для этих браузеров нужно подгружать polyfill:
if (!document.createElement('dialog').showModal) {
// Браузер нативно не поддерживает элемент dialog
import('/dist/dialog-polyfill.js') // Подгружаем polyfill
.then(dialogPolyfill =>
document.querySelectorAll('dialog')
.forEach(dialogPolyfill.registerDialog) // Применяем его для всех элементов на странице
)
}
Вы, конечно, можете использовать и другой элемент для реализации диалогового окна, например так:
<section role="dialog" aria-modal="true">
...
</section>
но тогда вам придётся самостоятельно реализовывать всё поведение описанное далее. В то время как с <dialog> большую часть браузер реализует из коробки.
Внешний вид и содержание
Вскользь коснусь внешнего вида.
На небольших экранах диалоговое окно должно занимать 100% его размера. Если ваш диалог будет большим:
- Его будет легче "нащупать". Дело в том, что пользователь может взаимодействовать со страницей следующим образом: он водит пальцем по дисплею, а программа чтения с экрана озвучивает то, что в данный момент находится под пальцем.
- Пользователю гарантированно не будут озвучиваться элементы "под ним". Иначе, например, VoiceOver на iPad может озвучивать отдельные фрагменты страницы под модальным окном даже "сквозь" оверлей блокирующий доступ указателю.
- Вы скроете прокрутку фона на некоторых устройствах при прокрутке контента в диалоговом окне.
- Удобнее для одной руки. Если окно растянуто на всю высоту – то у вас есть возможность прижать кнопки управления к нижней части дисплея. Туда намного проще дотянуться одной рукой пользователям современных смартфонов.
- Больше места для контента на устройствах с маленьким экраном, таких как iPhone SE.
Заголовок обязателен
У модального окна, как у любой обычной страницы, должен быть свой заголовок. Короткий, точно описывающий его предназначение. Наличие заголовка намного упрощает восприятие пользователем.
Настоятельно рекомендуется использовать для заголовка тег <h1>-<h6>.
Но просто добавить заголовок в диалоговое окно недостаточно. Их нужно ещё и логически "связать". Сделать это можно с помощью атрибута aria-labelledby следующим образом:
<dialog aria-labeledby="subscribe-header">
<h2 id="subscribe-header">Предложение подписки</h2>
</dialog>
Теперь, при попадании пользователя в диалоговое окно, в случае с экранным диктором, будет зачитан не только факт наличия диалога, но и его заголовок.
Статический контент должен быть связан с окном
Если в вашем диалоговом окне есть какое-то не интерактивное содержание, например, абзац текста, его стоит связать с диалогом подобно заголовку. Иначе, в некоторых случаях программы чтения с экрана не будут озвучивать такой контент.
Делается это атрибутом aria-describedby:
<dialog aria-labeledby="subscribe-header" aria-describedby="subscribe-content">
<h2 id="subscribe-header">Предложение подписки</h2>
<p id="subscribe-content">
Вы можете подписаться на нашу еженедельную рассылку.
В ней представлены только лучшие публикации.
</p>
</dialog>
Если в вашем диалоговом окне много контента, тогда стоит обернуть его в один <div> и связать элемент диалога уже с ним:
<dialog aria-labeledby="subscribe-header" aria-describedby="subscribe-content">
<h2 id="subscribe-header">Условия подписки</h2>
<div id="subscribe-content">
<p>Ниже представлены условия нашей подписки.</p>
<p>...</p>
<ul>...</ul>
<p>...</p>
...Много контента
</div>
</dialog>
Важно! Заголовок и любые кнопки не относящиеся к содержимому, а служащие для управления диалоговым окном, не должны быть включены в элемент на который указывает aria-describedby. Они должны быть вынесены отдельно:
<dialog aria-labeledby="subscribe-header" aria-describedby="subscribe-content">
<h2 id="subscribe-header">Условия подписки</h2>
<div id="subscribe-content">
<p>Ниже представлены условия нашей подписки.</p>
<p>...</p>
<ul>...</ul>
<p>...</p>
...Много контента
</div>
<div>
<button>Принять</button>
<button>Отказаться и закрыть</button>
</div>
</dialog>
Интерактивные элементы связывать не нужно
Есть другой сценарий, когда содержимое вашего окна состоит из формы без предшествующего ей текста. В таком случае нет необходимости связывать форму с окном:
<dialog aria-labeledby="subscribe-header">
<h2 id="subscribe-header">Данные для подписки</h2>
<form>
<label>
Введите ваш email
<input type="email">
</label>
</form>
<div>
<button>Подписаться</button>
<button>Отказаться и закрыть</button>
</div>
</dialog>
Элементы формы являются интерактивными. И они будут озвучены скринридером, когда пользователь начнёт с ними взаимодействовать.
Если скомбинировать и статический текст и форму:
<dialog aria-labeledby="subscribe-header" aria-describedby="subscribe-content">
<h2 id="subscribe-header">Подпишитесь на рассылку</h2>
<p id="subscribe-content">
Вы можете подписаться на нашу еженедельную рассылку.
</p>
<form>
<label>
Введите ваш email
<input type="email">
</label>
</form>
<div>
<button>Подписаться</button>
<button>Отказаться и закрыть</button>
</div>
</dialog>
Способы закрыть окно
Внутри диалогового окна обязана быть кнопка чтобы его закрыть. Не <div>, не <span> не <a>, не любой другой тег. Исключительно <button>. Это самый надежный способ гарантировать, что любой пользователь сможет закрыть диалоговое окно. Вы же не любите модальные окна которые невозможно закрыть?
Дополнительно, в зависимости от вашей логики, вы можете позволить пользователю закрыть диалог кликнув за его пределами или нажав Escape (встроено в <dialog> из коробки).
Но:
- Не рассчитывайте, что пользовать всегда может нажать на оверлей и так закрыть диалог.
- Как я писал ранее, во многих случаях диалоговое окно может занимать всю или большую часть экрана. Таким образом попасть в него может быть сложно или невозможно.
- Такой оверлей семантически не считается интерактивным элементом. Он не может быть в фокусе и на него невозможно "нажать" клавишами.
- Не рассчитывайте, что у пользователя под рукой есть клавиатура, чтобы нажать Escape.
- Существует множество устройств, программ и различных инструментов, способных читать веб-сайты и давать пользователю взаимодействовать с ними, но не так как в браузере. Во многих случаях единственным рабочим вариантом остаётся кнопка внутри.
Простейшая реализация кнопки закрывающей родительский диалог:
<button onclick="this.closest('dialog').close()">
Закрыть
</button>
А если вы делаете кнопку с иконкой, то не забывайте про подпись, чтобы передать ёё назначение:
<button onclick="this.closest('dialog').close()" aria-label="Закрыть">
×
</button>
Поведение фокуса
При открытии диалога
Во время открытия диалогового окна фокус должен быть перемещён на элемент внутри него. На какой именно — зависит от содержания.
В общем случае фокус перемещается на первый интерактивный элемент. Именно так ведет себя нативный <dialog> в браузере. Но нельзя делать сам элемент окна фокусируемым и перемещать фокус на него.
Например, для диалога с формой первый интерактивный элемент это первый <input>. Если ваше диалоговое окно носит чисто информативный характер, например, уведомление об успешной подписке, тогда первым и единственным элементом будет кнопка закрывающая диалог.
Но есть и несколько исключений:
- Запрос подтверждения чего-либо. Если ваш диалог запрашивает у пользователя подтверждения перед выполнением каких-то необратимых действий (удаление чего-то или выполнение финансовых операций), тогда фокус автоматически должен ставится на кнопку "отмены" этих действий, независимо от её расположения.
- Ситуации, когда в диалоговом окне много статического контента и первый интерактивный элемент не помещается в видимую область. Проблема тут в том, что в таком случае браузер автоматически проскролит вниз к кнопке в фокусе. Это вынудит пользователя скролить обратно вверх, а потом снова вниз. Для таких случаев есть два подхода:
- Переместить или продублировать интерактивные элементы так, чтобы первый из них был в видимой части экрана. Например, выполнить кнопку закрыть в виде крестика и закрепить в верхней части диалогового окна.
- Заголовок или первый абзац текста нужно сделать фокусируемым при помощи tabindex="-1" и перемещать фокус на него. Но при этом подходе некоторые программы чтения с экрана могут озвучивать заданный текст дважды: сначала как заголовок и описание окна, а потом как содержание выделенного элемента.
Управлять куда именно попадёт фокус при открытии модального окна можно с помощью атрибута autofocus:
<dialog aria-labeledby="subscribe-header">
<h2 id="subscribe-header">Необратимые действия</h2>
<form>
<label>
Введите пароль для подтверждения
<input type="password">
</label>
</form>
<div>
<button>Подтверждаю</button>
<button autofocus>Отказаться и закрыть</button> <!-- Будет выбрана эта кнопка -->
</div>
</dialog>
Внутри диалога
Особенность модального окна в том, что оно перекрывает собой весь документ не давая возможность с ним взаимодействовать.
Чтобы блокировать указатель обычно документ накрывается полупрозрачным блоком.
Но этого недостаточно, так как остаётся ещё и навигация клавишами Tab / Shift + Tab. Также это могут быть клавиши громкости на смартфонах или специальные клавиши на дополнительных инструментах подключенных по USB/Bluetooth. Этот способ навигации тоже должен быть заблокирован.
После попадания фокуса в модальное окно пользователь может перебирать интерактивные элементы внутри этого окна, но не должен выходить за его пределы. Другими словами, такое диалоговое окно работает как ловушка для фокуса. Это поведение встроено в <dialog>, так что от вас никаких действий не требуется. А вот используя другой элемент с role="dialog" его нужно реализовывать самостоятельно средствами JavaScript.
При закрытии диалога
При закрытии диалогового окна фокус должен быть перемещён туда, где он был в момент открытия. Это поведение не является частью <dialog> и браузер полностью оставляет это на усмотрение разработчика.
Но и тут есть одно исключение: если элемент более не доступен, тогда фокус нужно вернуть туда, откуда наиболее логично для пользователя продолжить работу.
Пример
Предлагаю разобрать на примере. Представим систему из трех диалоговых окон:
- Сообщает пользователю об наличии подписки. В нем две кнопки: "Условия подписки" и "Подписаться"
- Отображается по клику на "Условия подписки". Открывается поверх первого.
- Отображается по клику на "Подписаться". Заменяет собой первое.
В примерах ниже я специально пропустил дополнительные атрибуты и элементы, для упрощения кода.
Итак, у нас есть стартовая кнопка.
<button>Рассылка</button> <!-- in focus -->
По нажатию на неё открывается первый диалог. Фокус автоматически перемещается на первый интерактивный элемент. А закрытие диалога должно возвращать фокус назад.
┌►<button>Рассылка</button>
│
└─ <dialog open>
<button>Подписаться</button> <!-- in focus -->
<button>Условия подписки</button>
</dialog>
Далее пользователь перемещает фокус на "Условия подписки" и нажимает. Открывается второй диалог поверх первого. Фокус перемещается в него, а возвращаться должен на эту же кнопку в первом диалоге:
┌►<button>Рассылка</button>
│
└─ <dialog open>
<h2>Рассылка</h2>
<button>Подписаться</button>
┌────► <button>Условия подписки</button>
│ </dialog>
│
└─ <dialog open>
<h2>Условия подписки</h2>
<button>Ок</button> <!-- in focus -->
</dialog>
После закрытия второго диалога ваш JavaScript должен вернуть фокус на кнопку "Условия подписки" в первом.
┌►<button>Рассылка</button>
│
└─ <dialog open>
<button>Подписаться</button>
<button>Условия подписки</button> <!-- in focus -->
</dialog>
После чего пользователь нажимает кнопку "Подписаться". По условиям нашей задачи открывается третий диалог. Фокус автоматически перемещается в него. А первый диалог закрывается:
┌►<button>Рассылка</button>
│
└─ <dialog>
<h2>Рассылка</h2>
┌────× <button>Подписаться</button>
│ <button>Условия подписки</button>
│ </dialog>
│
│ <dialog>
│ <h2>Условия подписки</h2>
│
│ <button>Ок</button>
│ </dialog>
│
└─ <dialog open>
<h2>Введите email</h2>
<button>Подтвердить</button> <!-- in focus -->
</dialog>
И вот проблема: третье окно должно вернуть фокус на кнопку в первом, но первое окно больше не доступно. В таких случаях фокус нужно вернуть туда, куда указывал закрытый диалог — на кнопку "Рассылка" с которой пользовать начал.
┌►<button>Рассылка</button>
│
│ <dialog>
│ <h2>Рассылка</h2>
│
│ <button>Подписаться</button>
│ <button>Условия подписки</button>
│ </dialog>
│
│ <dialog>
│ <h2>Условия подписки</h2>
│
│ <button>Ок</button>
│ </dialog>
│
└─ <dialog open>
<h2>Введите email</h2>
<button>Подтвердить</button> <!-- in focus -->
</dialog>
Безусловно, в вашем конкретном случае может быть более логичное поведение для возвращения фокуса. Например, у вас диалог создания новой записи в таблице. В таком случае, может быть логичнее возвращать фокус на только что созданную запить.
Помните, как во время установки программы в Windows можно просто нажимать Enter? Так вот это пример хорошей работы с фокусом: каждый раз, при переходе на новый экран в фокус ставится элемент, с которым вы скорее всего будете взаимодействовать — кнопка "Далее" или "Обзор".
Подводя итог
- Используйте семантические теги <button> и <dialog>.
- Делайте ваши окна достаточно большими.
- В модальных окнах должен быть заголовок.
- Заголовок и содержимое должны быть соответствующим образом связаны с элементом модального окна.
- Убедитесь что в диалоге есть кнопка для закрытия окна.
- Перемещайте фокус внутрь при открытии, не выпускайте его из модального окна и возвращайте туда, где он был после закрытия.
Дополнительные ссылки
- Modal Dialog Design Pattern in WAI-ARIA Authoring Practices 1.2
- HTML Living Standard
- Polyfill for the HTML dialog element
- Filament group Modal
===========
Источник:
habr.com
===========
Похожие новости:
- [JavaScript, WebGL, Работа с 3D-графикой] Продолжаем чистить память с three.js
- [JavaScript, Программирование, Разработка веб-сайтов] Drag'n'Drop API: пример использования
- [Дизайн, Веб-дизайн, Интерфейсы, Usability, Дизайн мобильных приложений] Выводы, которые я сделал помогая стартапу для секс-чатов повысить конверсию
- [JavaScript, Angular, ReactJS, TypeScript] Простые TypeScript-хитрости, которые позволят масштабировать ваши приложения бесконечно
- [Разработка веб-сайтов, JavaScript, Программирование, VueJS] Vue 3.0 — первый взгляд
- [Веб-дизайн, Интерфейсы, Usability, Исследования и прогнозы в IT, Развитие стартапа] Человек и кино. Исследования
- [C, JavaScript, Интернет вещей, Программирование микроконтроллеров, Разработка для интернета вещей] Термостат на ThingJS (beta)
- [JavaScript, Алгоритмы, Программирование] Нестабильная сортировка в JavaScript
- [JavaScript] «Никогда не писали автотесты? Попробуйте Cypress»
- [JavaScript, VueJS] Как я умный аквариум делал (frontend)
Теги для поиска: #_javascript, #_html, #_usability, #_accessibility, #_html, #_javascript, #_dialog, #_modal_dialog, #_accessibility, #_javascript, #_html, #_usability, #_accessibility
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 01-Ноя 04:31
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 8 месяцев |
|
Уверен, многие хоть раз создавали всплывающее модальное окно. Но задумывались ли вы об определении этого компонента? Как он должен работать? В этом материале я постарался собрать максимально полный свод правил, рекомендаций и примеров реализации по которым модальные окна должны работать. Я покажу, как просто создавать сложные, удобные, производительные и доступные модальные окна независимо от браузера, платформы, устройства или способа взаимодействия пользователя. Этот список сформирован на основе спецификаций WAI-ARIA, HTML Living Standard и моего личного опыта. И хотя я буду говорить про веб, большинство правил и рекомендаций применимы для модальных окон где угодно. Определение модального окна Модальное окно — это окно наложенное либо на документ, либо на другие окна. При этом, любой контент под модальным окном является недоступным для взаимодействия. Теги и атрибуты Интерактивным элементом для открытия диалогового окна должна выступать кнопка. Не <div>, не <span> не <a>, не любой другой тег. Исключительно <button>. И касается не только диалоговых окон, <button> — самый надежный и доступный способ создавать интерактивные элементы на странице. Простейшая реализация кнопки открывающая диалог по его id: <button data-modal="dialogId" onclick="document.getElementById(this.dataset.modal).showModal()">
Открыть </button> <dialog> Для различных диалогов, уведомлений и прочих перекрывающих документ элементов существует тег <dialog>. Его вы и должны использовать. К огромному сожалению, его поддержка не самая лучшая:
Так что для этих браузеров нужно подгружать polyfill: if (!document.createElement('dialog').showModal) {
// Браузер нативно не поддерживает элемент dialog import('/dist/dialog-polyfill.js') // Подгружаем polyfill .then(dialogPolyfill => document.querySelectorAll('dialog') .forEach(dialogPolyfill.registerDialog) // Применяем его для всех элементов на странице ) } Вы, конечно, можете использовать и другой элемент для реализации диалогового окна, например так: <section role="dialog" aria-modal="true">
... </section> но тогда вам придётся самостоятельно реализовывать всё поведение описанное далее. В то время как с <dialog> большую часть браузер реализует из коробки. Внешний вид и содержание Вскользь коснусь внешнего вида. На небольших экранах диалоговое окно должно занимать 100% его размера. Если ваш диалог будет большим:
Заголовок обязателен У модального окна, как у любой обычной страницы, должен быть свой заголовок. Короткий, точно описывающий его предназначение. Наличие заголовка намного упрощает восприятие пользователем. Настоятельно рекомендуется использовать для заголовка тег <h1>-<h6>. Но просто добавить заголовок в диалоговое окно недостаточно. Их нужно ещё и логически "связать". Сделать это можно с помощью атрибута aria-labelledby следующим образом: <dialog aria-labeledby="subscribe-header">
<h2 id="subscribe-header">Предложение подписки</h2> </dialog> Теперь, при попадании пользователя в диалоговое окно, в случае с экранным диктором, будет зачитан не только факт наличия диалога, но и его заголовок. Статический контент должен быть связан с окном Если в вашем диалоговом окне есть какое-то не интерактивное содержание, например, абзац текста, его стоит связать с диалогом подобно заголовку. Иначе, в некоторых случаях программы чтения с экрана не будут озвучивать такой контент. Делается это атрибутом aria-describedby: <dialog aria-labeledby="subscribe-header" aria-describedby="subscribe-content">
<h2 id="subscribe-header">Предложение подписки</h2> <p id="subscribe-content"> Вы можете подписаться на нашу еженедельную рассылку. В ней представлены только лучшие публикации. </p> </dialog> Если в вашем диалоговом окне много контента, тогда стоит обернуть его в один <div> и связать элемент диалога уже с ним: <dialog aria-labeledby="subscribe-header" aria-describedby="subscribe-content">
<h2 id="subscribe-header">Условия подписки</h2> <div id="subscribe-content"> <p>Ниже представлены условия нашей подписки.</p> <p>...</p> <ul>...</ul> <p>...</p> ...Много контента </div> </dialog> Важно! Заголовок и любые кнопки не относящиеся к содержимому, а служащие для управления диалоговым окном, не должны быть включены в элемент на который указывает aria-describedby. Они должны быть вынесены отдельно: <dialog aria-labeledby="subscribe-header" aria-describedby="subscribe-content">
<h2 id="subscribe-header">Условия подписки</h2> <div id="subscribe-content"> <p>Ниже представлены условия нашей подписки.</p> <p>...</p> <ul>...</ul> <p>...</p> ...Много контента </div> <div> <button>Принять</button> <button>Отказаться и закрыть</button> </div> </dialog> Интерактивные элементы связывать не нужно Есть другой сценарий, когда содержимое вашего окна состоит из формы без предшествующего ей текста. В таком случае нет необходимости связывать форму с окном: <dialog aria-labeledby="subscribe-header">
<h2 id="subscribe-header">Данные для подписки</h2> <form> <label> Введите ваш email <input type="email"> </label> </form> <div> <button>Подписаться</button> <button>Отказаться и закрыть</button> </div> </dialog> Элементы формы являются интерактивными. И они будут озвучены скринридером, когда пользователь начнёт с ними взаимодействовать. Если скомбинировать и статический текст и форму: <dialog aria-labeledby="subscribe-header" aria-describedby="subscribe-content">
<h2 id="subscribe-header">Подпишитесь на рассылку</h2> <p id="subscribe-content"> Вы можете подписаться на нашу еженедельную рассылку. </p> <form> <label> Введите ваш email <input type="email"> </label> </form> <div> <button>Подписаться</button> <button>Отказаться и закрыть</button> </div> </dialog> Способы закрыть окно Внутри диалогового окна обязана быть кнопка чтобы его закрыть. Не <div>, не <span> не <a>, не любой другой тег. Исключительно <button>. Это самый надежный способ гарантировать, что любой пользователь сможет закрыть диалоговое окно. Вы же не любите модальные окна которые невозможно закрыть? Дополнительно, в зависимости от вашей логики, вы можете позволить пользователю закрыть диалог кликнув за его пределами или нажав Escape (встроено в <dialog> из коробки). Но:
Простейшая реализация кнопки закрывающей родительский диалог: <button onclick="this.closest('dialog').close()">
Закрыть </button> А если вы делаете кнопку с иконкой, то не забывайте про подпись, чтобы передать ёё назначение: <button onclick="this.closest('dialog').close()" aria-label="Закрыть">
× </button> Поведение фокуса При открытии диалога Во время открытия диалогового окна фокус должен быть перемещён на элемент внутри него. На какой именно — зависит от содержания. В общем случае фокус перемещается на первый интерактивный элемент. Именно так ведет себя нативный <dialog> в браузере. Но нельзя делать сам элемент окна фокусируемым и перемещать фокус на него. Например, для диалога с формой первый интерактивный элемент это первый <input>. Если ваше диалоговое окно носит чисто информативный характер, например, уведомление об успешной подписке, тогда первым и единственным элементом будет кнопка закрывающая диалог. Но есть и несколько исключений:
Управлять куда именно попадёт фокус при открытии модального окна можно с помощью атрибута autofocus: <dialog aria-labeledby="subscribe-header">
<h2 id="subscribe-header">Необратимые действия</h2> <form> <label> Введите пароль для подтверждения <input type="password"> </label> </form> <div> <button>Подтверждаю</button> <button autofocus>Отказаться и закрыть</button> <!-- Будет выбрана эта кнопка --> </div> </dialog> Внутри диалога Особенность модального окна в том, что оно перекрывает собой весь документ не давая возможность с ним взаимодействовать. Чтобы блокировать указатель обычно документ накрывается полупрозрачным блоком. Но этого недостаточно, так как остаётся ещё и навигация клавишами Tab / Shift + Tab. Также это могут быть клавиши громкости на смартфонах или специальные клавиши на дополнительных инструментах подключенных по USB/Bluetooth. Этот способ навигации тоже должен быть заблокирован. После попадания фокуса в модальное окно пользователь может перебирать интерактивные элементы внутри этого окна, но не должен выходить за его пределы. Другими словами, такое диалоговое окно работает как ловушка для фокуса. Это поведение встроено в <dialog>, так что от вас никаких действий не требуется. А вот используя другой элемент с role="dialog" его нужно реализовывать самостоятельно средствами JavaScript. При закрытии диалога При закрытии диалогового окна фокус должен быть перемещён туда, где он был в момент открытия. Это поведение не является частью <dialog> и браузер полностью оставляет это на усмотрение разработчика. Но и тут есть одно исключение: если элемент более не доступен, тогда фокус нужно вернуть туда, откуда наиболее логично для пользователя продолжить работу. Пример Предлагаю разобрать на примере. Представим систему из трех диалоговых окон:
В примерах ниже я специально пропустил дополнительные атрибуты и элементы, для упрощения кода. Итак, у нас есть стартовая кнопка. <button>Рассылка</button> <!-- in focus -->
По нажатию на неё открывается первый диалог. Фокус автоматически перемещается на первый интерактивный элемент. А закрытие диалога должно возвращать фокус назад. ┌►<button>Рассылка</button>
│ └─ <dialog open> <button>Подписаться</button> <!-- in focus --> <button>Условия подписки</button> </dialog> Далее пользователь перемещает фокус на "Условия подписки" и нажимает. Открывается второй диалог поверх первого. Фокус перемещается в него, а возвращаться должен на эту же кнопку в первом диалоге: ┌►<button>Рассылка</button>
│ └─ <dialog open> <h2>Рассылка</h2> <button>Подписаться</button> ┌────► <button>Условия подписки</button> │ </dialog> │ └─ <dialog open> <h2>Условия подписки</h2> <button>Ок</button> <!-- in focus --> </dialog> После закрытия второго диалога ваш JavaScript должен вернуть фокус на кнопку "Условия подписки" в первом. ┌►<button>Рассылка</button>
│ └─ <dialog open> <button>Подписаться</button> <button>Условия подписки</button> <!-- in focus --> </dialog> После чего пользователь нажимает кнопку "Подписаться". По условиям нашей задачи открывается третий диалог. Фокус автоматически перемещается в него. А первый диалог закрывается: ┌►<button>Рассылка</button>
│ └─ <dialog> <h2>Рассылка</h2> ┌────× <button>Подписаться</button> │ <button>Условия подписки</button> │ </dialog> │ │ <dialog> │ <h2>Условия подписки</h2> │ │ <button>Ок</button> │ </dialog> │ └─ <dialog open> <h2>Введите email</h2> <button>Подтвердить</button> <!-- in focus --> </dialog> И вот проблема: третье окно должно вернуть фокус на кнопку в первом, но первое окно больше не доступно. В таких случаях фокус нужно вернуть туда, куда указывал закрытый диалог — на кнопку "Рассылка" с которой пользовать начал. ┌►<button>Рассылка</button>
│ │ <dialog> │ <h2>Рассылка</h2> │ │ <button>Подписаться</button> │ <button>Условия подписки</button> │ </dialog> │ │ <dialog> │ <h2>Условия подписки</h2> │ │ <button>Ок</button> │ </dialog> │ └─ <dialog open> <h2>Введите email</h2> <button>Подтвердить</button> <!-- in focus --> </dialog> Безусловно, в вашем конкретном случае может быть более логичное поведение для возвращения фокуса. Например, у вас диалог создания новой записи в таблице. В таком случае, может быть логичнее возвращать фокус на только что созданную запить. Помните, как во время установки программы в Windows можно просто нажимать Enter? Так вот это пример хорошей работы с фокусом: каждый раз, при переходе на новый экран в фокус ставится элемент, с которым вы скорее всего будете взаимодействовать — кнопка "Далее" или "Обзор". Подводя итог
Дополнительные ссылки
=========== Источник: habr.com =========== Похожие новости:
|
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 01-Ноя 04:31
Часовой пояс: UTC + 5