Знакомьтесь, модальное окно

Анна Селезнёва, Evil Martians

Знакомьтесь, модальное окно

Web Standards Web Standards Days, Минск, 21 октября 2017

Привет!

Меня зовут Аня, и я марсианка

askd.rocks/pres/rd-wsd

Элементы интерфейса

Модальное окно

В графическом интерфейсе пользователя модальным называется окно, которое блокирует работу пользователя с родительским приложением до тех пор, пока пользователь это окно не закроет.

Википедия

Цель модального окна

Раздражающее окно

Это работает

И пользователи не жаловались

План

  1. Стандартное окно
  2. Простое окно
  3. Удобное окно
  4. Улучшенное окно

1. Стандартное окно

История

alert

			alert('Resistance is futile');
		
alert

confirm

			confirm('Resistance is futile');
		
confirm

prompt

			prompt('Resistance is futile', 'Surrender');
		
prompt

Рекомендация Google

Не используйте alert / confirm / prompt

developers.google.com/web/updates/2017/03/dialogs-policy

showModalDialog

			window.showModalDialog('modal-dialog.html');
		

Safari   Пример

deprecated

Будущее Но это не точно

dialog

			<dialog open class="dialog">
  <p>Resistance is futile</p>
</dialog>
		
dialog

dialog

Показать:

			document.querySelector('dialog').showModal();
		

Спрятать:

			document.querySelector('dialog').close();
		

dialog

CSS:

			.modal::backdrop {
	background-color: rgba(0, 0, 0, 0.7);
}
		

Пример: codepen.io/askd/pen/zEjOaJ

dialog

Спецификации:

Живой стандарт WHATWG HTML
Рабочий черновик HTML5.1

Поддержка:

Google Chrome Opera

Полифил: github.com/GoogleChrome/dialog-polyfill

2. Простое окно

Кратко

			<div class="modal">
  <p>Resistance is futile</p>
</div>
		

CSS:

			.modal { display: none; }
.modal.is-opened { display: block; }
		

A11y: Как прятать элемент

  1. Убрать из a11y-дерева: aria-hidden="true"
  2. Исключить из порядка навигации с клавиатуры: tabindex="-1"
  3. Скрыть визуально: class="visuallyhidden"
			.visuallyhidden {
	border: 0;
	clip: rect(0 0 0 0);
	clip-path: inset(50%);
	height: 1px;
	margin: -1px;
	overflow: hidden;
	padding: 0;
	position: absolute;
	width: 1px;
	white-space: nowrap;
}
		

A11y: Атрибуты

			<div class="modal" role="dialog"
	aria-labelledby="modalTitle"
	aria-describedby="modalDesc"
>
  <h2 id="modalTitle">Заголовок окна</h2>
  <p id="modalDesc">Описание окна</p>
</div>
			
		

Без JS (:checked)

			<input type="checkbox" id="toggle">
<div class="modal">
	...
	<label for="toggle">✕</label>
</div>

<label for="toggle">Показать модальное окно</label>
		

Без JS (:checked)

CSS:

			#toggle:checked ~ .modal { display: block; }
		

Пример: codepen.io/askd/pen/QqrPdo

Без JS (:target)

			<div class="modal" id="modal">
	...
	<a href="#">✕</a>
</div>

<a href="#modal">Показать модальное окно</a>
		

Без JS (:target)

CSS:

			#modal:target { display: block; }
		

Пример: codepen.io/askd/pen/PJyoLZ

Лучше с JS

Хорошее модальное окно невозможно сделать на чистом CSS

Анатомия

Модальное окно

UX: Понятные заголовки

Вы уверены?
Да
Нет
Удалить заказ?
Подтвердить
Отменить

Оверлей

Paranja
Facebook overlay
Twitter overlay

Оверлей

			.overlay {
	position: fixed;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	background-color: rgba(0, 0, 0, 0.7);
}
		

Окно с оверлеем

			<div class="modal overlay">
	<div class="modal-content"> ... </div>
</div>
		
			.modal {
	display: flex;
	justify-content: center;
	align-items: center;
}
		

UX: Только одно окно

Несколько модальных окон

3. Удобное окно

Открытие окна

Закрытие окна

A11y: ✕ – это кнопка

			<button>✕</button>
		

или

			<span role="button" tabindex="0">✕</span>
		

A11y: ✕ – это «Закрыть»

			<button aria-label="Закрыть"></button>
		

или

			<button aria-label="Закрыть" class="close"></button>
		

UX: Отменяемый Escape

			function handleKeyDown (event) {
	const keyCode = event.keyCode || event.which;
	if (keyCode === 27) {
		// закрыть модальное окно
		event.preventDefault();
	}
}
document.addEventListener('keydown', handleKeyDown);
			
		

Перехват фокуса

Когда открыто модальное окно, навигация с клавиатуры не должна выходить за его пределы

Способ 1: inert

			<div inert>
	<input type="text" placeholder="Недоступное поле">
	<button>Некликабельная кнопка</button>
</div>
			
		

Полифил: github.com/GoogleChrome/inert-polyfill

Способ 2: focus-lock

github.com/theKashey/focus-lock

			setFocus(topNode, lastNode)
		

ReactJS:

			<FocusLock><Modal /></FocusLock>
		

Подробнее в статье

4. Улучшенное окно

Окно со скроллом

			.modal {
	 overflow: auto;
}

body {
	overflow: hidden;
}
		

Окно со скроллом

Мобильная версия

			body { position: fixed; }
		

JS

			const top = window.pageYOffset || documentElement.scrollTop;
body.style.top = `-${top}px`;
		

UX: Скролла быть не должно

Если для содержимого требуется скролл, то скорее всего лучше использовать не модальное окно, а отдельную страницу

Анимации

Анимация А Б

Анимации появления

tympanus.net/Development/ModalWindowEffects

Эффекты появления

askd.rocks/pres/frontend/

Сайдбар – модальное окно

tympanus.net/Development/SidebarTransitions

eBay Social backdrop-filter

Экспериментальная технология

			backdrop-filter: blur(8px);
		

Поддержка:

Safari   -webkit

React Portal

			<div id="app-root"></div>
		

CSS:

			#app-root { overflow: hidden; }
		

React Portal

			<div id="app-root"></div>
<div id="modal-root"></div>
		

JS:

			ReactDOM.createPortal(modal, modalRoot);
		

Пример: codepen.io/askd/pen/WZJqMz

Итого

Хорошее модальное окно

Спасибо!

asktwi

anna.selezniova

askd.rocks


Презентация: askd.rocks/pres/modal