Вам не нужен для этого JavaScript

в 13:00, , рубрики: accessibility, html, ruvds_переводы, доступность сайта, интерактивный дизайн, специальные возможности, формы

Вам не нужен для этого JavaScript - 1


Прошу вас не возмущаться названием статьи. Я не ненавижу JavaScript, я люблю его. Ежедневно я пишу на нём кучу кода. Но ещё я люблю CSS и даже люблю JSX HTML. Я люблю все эти три технологии по причине, которая называется…

▍ Правило наименьших полномочий

Это один из базовых принципов веб-разработки, означающий, что следует выбирать наименее мощный язык, подходящий для решения задачи.

В случае веба это означает, что нужно по возможности выбирать HTML вместо CSS, а затем CSS вместо JS. JS — самый универсальный язык из всех трёх, потому что на нём вы описываете, как должен вести себя браузер; но также он может ломаться, отказываться загружаться, требует дополнительных ресурсов для скачивания, парсинга и исполнения. Кроме того, при его использовании очень легко ограничить доступ пользователей, выполняющих браузинг при помощи клавиатуры или специальных возможностей.

В отличие от JS с его императивностью, HTML и CSS декларативны. Вы говорите браузеру, что делать, а не как это делать. Это значит, что браузер сам выбирает, как это делать, и может сделать это наиболее эффективным образом.

Так как функции HTML и CSS обрабатываются браузером, они могут быть более производительными, более нативными, более адаптируемыми к предпочтениям пользователя и в общем случае иметь бОльшую accessibility. Это не значит, что так будет всегда (особенно когда дело касается accessibility), но когда все сложные задачи берёт на себя браузер, от этого обычно выигрывают конечные пользователи.

▍ Но мне нужен для этого JS!

Возможно, вы подумаете: «Во всём, для чего я использую JS, он необходим». Это может быть правдой, но неплохо помнить о том, что разработчики браузеров и авторы спецификаций портировали в CSS и HTML большую часть функциональности, для которое ещё несколько лет назад требовался JS. Именно этому и посвящена моя статья.

Особенность веб-разработки заключается в том, что, научившись делать что-то один раз, вы больше не будете иметь никаких причин учиться делать это заново. Таков контракт, который мы заключаем: веб имеет обратную совместимость. Существуют очень незначительные исключения, но первая веб-страница по-прежнему отлично отображается во всех современных браузерах.

Ещё это означает, что найденное однажды вами решение становится частью вашего инструментария, вы продолжаете повторно реализовывать его, и оно каждый раз срабатывает. Приведённые в статье примеры полезны (поэтому я их и привожу), но я хочу донести, что даже если вы знаете о необходимости JavaScript для решения задачи, это не значит, что он по-прежнему требуется. Если вы часто будете проверять свои допущения, то сможете создавать более качественные веб-сайты.

▍ Собственные переключатели

Мы начнём статью с того, что всем нам когда-то приходилось реализовывать — с переключателей. Вместо обычного чекбокса в дизайне иногда необходим красиво выглядящий переключатель. Мы не будем искать решение на JS с div, обработчиками onclick и внутренним состоянием, а воспользуемся обычным чекбоксом и псевдоклассом :checked. Вот HTML, который мы будем использовать:

<label>
  <input type="checkbox" />
  My awesome feature
</label>

Здесь есть элемент label, а внутри него находится чекбокс. Удобство этого в том, что браузер сам делает всё за нас. Так как наш input находится внутри label, браузер связывает их, и теперь мы можем нажать на любую точку label для переключения чекбокса, а обработчик onclick не требуется. Браузер дал нам его «бесплатно». С точки зрения функциональности мы выполнили задачу.

Вам не нужен для этого JavaScript - 2

Разумеется, дизайнерам может не понравиться внешний вид, и вам придётся создавать красивый переключатель. Поэтому добавим немного CSS:

input {
  appearance: none;
  position: relative;
  display: inline-block;
  background: lightgrey;
  height: 1.65rem;
  width: 2.75rem;
  vertical-align: middle;
  border-radius: 2rem;
  box-shadow: 0px 1px 3px #0003 inset;
  transition: 0.25s linear background;
}
input::before {
  content: "";
  display: block;
  width: 1.25rem;
  height: 1.25rem;
  background: #fff;
  border-radius: 1.2rem;
  position: absolute;
  top: 0.2rem;
  left: 0.2rem;
  box-shadow: 0px 1px 3px #0003;
  transition: 0.25s linear transform;
  transform: translateX(0rem);
}

Все подробности стилизации здесь не так важны, но я бы хотел обратить ваше внимание на первое правило: appearance: none.

Элементы форм вместе с изображениями — это нечто, называемое «заменяемым контентом». Это значит, что на самом деле они не являются частью HTML, а передаются браузером. Когда браузер рендерит HTML и находит заменяемый контент, он оставляет для него прямоугольник, а затем заменяет этот прямоугольник настоящим контентом. Именно поэтому, например, изображения и элементы форм не имеют псевдоэлементов: они заменяются, когда браузер заменяет весь элемент.

appearance приказывает браузеру не делать этого. Это правило говорит браузеру: «Спасибо, но я хочу стилизовать собственный элемент управления формой». И это позволяет нам затем использовать псевдоэлемент ::before. Сам input теперь становится фоном для нашего переключателя, а псевдоэлемент ::before — это маленькая точка внутри него, выполняющая переключение.

Вам не нужен для этого JavaScript - 3

При нажатии мы по-прежнему переключаем чекбокс, но поскольку мы заменили элемент, нам нужно самостоятельно сделать его видимым. И тут нам на помощь приходит псевдокласс :checked:

:checked {
  background: green;
}
:checked::before {
  transform: translateX(1rem);
}

При нажатии на чекбокс псевдокласс :checked выполняет проверку, вызывая обновление стилизации.

Вам не нужен для этого JavaScript - 4

Итак, у нас теперь есть красивый переключатель, созданный при помощи нативных элементов HTML и доли CSS, но мы ещё не закончили. Пользователям, которые работают с мышью, понятно, с каким элементом управления формы они взаимодействуют, но для людей, работающих с клавиатуры, всё не так очевидно.

Я уверен, что вы знакомы с этой частью CSS, позволяющей избавиться от некрасивого пунктирного прямоугольного контура.

input:focus {
  outline: none;
}

Знайте, что это плохая идея. Но как сделать контур красивее? В этой области браузеры тоже приготовили нам улучшения. Теперь outline следует за border-radius элемента; кроме того, мы можем сместить его в сторону или внутрь элемента:

input:focus-visible {
  outline: 2px solid dodgerblue;
  outline-offset: 2px;
}

Теперь если пользователь будет взаимодействовать с элементом с клавиатуры (можете попробовать нажать на пробел после нажатия на него, или добраться до него при помощи Tab), :focus-visible будет соответствовать ему (но не при использовании мыши), и пользователь получит красивый синий контур вокруг элемента.

Вам не нужен для этого JavaScript - 5

Наконец, я хочу заменить outline: none на нечто иное:

input:focus {
  outline-color: transparent;
}

Результат будет таким же: контур окажется невидимым не потому, что он скрыт, а потому, что он прозрачен. Однако для пользователей с включенным высококонтрастным режимом (также называемым forced colors) этот контур снова становится видимым, потому что в высококонтрастном режиме этот прозрачный цвет заменяется выбранным пользователем, помогая ему видеть, с чем он взаимодействует даже при использовании мыши.

Чтобы не делать статью слишком длинной, я не буду объяснять, что делает forced-colors, но если вы хотите узнать подробности, то прочитайте мой пост Forced colors explained.

▍ Datalist с нативным автозаполнением

Вместо того, чтобы устанавливать $your-framework-autosuggest, попробуйте использовать в своём следующем проекте наш datalist. Datalist — это встроенный в браузеры способ отображения опций, когда пользователь что-то вводит в поле ввода.

<input list="frameworks" />

<datalist id="frameworks">
  <option>Bootstrap</option>
  <option>Tailwind CSS</option>
  <option>Foundation</option>
  <option>Bulma</option>
  <option>Skeleton</option>
</datalist>

Чтобы использовать его, достаточно добавить в HTML элемент datalist с ID и списком вариантов. Не волнуйтесь, элемент будет невидимым. Затем мы используем атрибут list для поля ввода, чтобы связать их.

Вам не нужен для этого JavaScript - 6

Если пользователь теперь будет печатать в поле ввода, то браузер отобразит datalist в виде раскрывающегося списка, автоматически фильтруя варианты при вводе. Однако так как это обычный input, пользователи всё равно могут вводить собственные значения. Также они могут просмотреть список всех вариантов, нажав на поле ввода и перемещаясь по списку при помощи клавиш со стрелками, или нажав на добавленный браузером значок раскрывающегося списка.

▍ Инструмент для выбора цвета с дополнительными возможностями

Существует множество красивых селекторов цветов с красивыми UI и ползунками, написанные в сотнях строк JavaScript. Но знали ли вы, что есть и нативный инструмент для выбора цветов?

<label> <input type="color" /> Color </label>

Эта одна строка HTML позволяет получить селектор цветов с красивым UI и сэкономить на куче JS. Но так как его обработкой занимается браузер, мы «бесплатно» получаем дополнительную функциональность. В браузерах Chromium этот нативный селектор цветов также позволяет выбрать цвет не только со своего сайта, но и из любого места экрана. Очень удобно!

Вам не нужен для этого JavaScript - 7

Небольшое примечание: несмотря на то, что браузеры отображают селектор цветов, пользователи могут и не увидеть его. Поэтому всё равно стоит предоставить другой способ выбора цвета (например, через обычный текстовый элемент ввода).

▍ Аккордеоны

Аккордеоны — отличный способ сделать страницу с большим количеством контента более структурированной и менее хаотичной; мы разбиваем контент в том порядке, в котором он понадобится пользователю. И браузеры «бесплатно» дают нам такую возможность при помощи элементов details и summary:

<details>
  <summary>My accordion</summary>
  <p>My accordion content</p>
</details>

Вам не нужен для этого JavaScript - 8

По умолчанию всё, что находится внутри элемента details, скрыто, за исключением summary. Когда пользователь нажимает на элемент summary, браузер отображает остальную часть контента.

Часто бывает так, что один элемент аккордеона открыт, а другие закрыты. Это можно реализовать при помощи атрибута open:

<details open>
  <summary>My accordion</summary>
  <p>My accordion content</p>
</details>

Вам не нужен для этого JavaScript - 9

Если вы работаете с React, то можете посмотреть на этот код и подумать: «Отлично, теперь у него есть свойство open и он больше не закроется»; к счастью, это не так. Атрибут open — это лишь начальное состояние, оно обновляется, когда пользователь взаимодействует с аккордеоном.

Со стилизацией нам тоже поможет элемент details. Маленький треугольник (от которого дизайнер захочет избавиться, как только его увидит) — это псевдоэлемент ::marker, который можно стилизовать:

summary::marker {
  font-size: 1.5em;
  content: "📬";
}
[open] summary::marker {
  font-size: 1.5em;
  content: "📭";
}

Вам не нужен для этого JavaScript - 10

Помните, что изменение контента может влиять на то, как специальные возможности сообщают об аккордеоне. Подробнее о несогласованностях details/summary можно прочитать в статье Мануэля. Кроме того, в Safari придётся использовать псевдоэлемент ::-webkit-details-marker.

Псевдоэлемент marker нельзя стилизовать так же подробно, как другие элементы (многие свойства CSS не работают с ним, например, размещение его в совершенно другом месте), но можно заменить его контент, например, на эмодзи, или установить цвет или изображение фона, а также поменять размер шрифта.

При помощи атрибута open можно легко придать ему стилизацию, отличающуюся от стилизации закрытого состояния.

Наконец, можно сделать что-нибудь с элементом summary. На него можно нажимать, но в отличие от ссылки он не превращает курсор в указатель, а в отличие от кнопки он не выглядит как кнопка. Поэтому я думаю, нужно добавить к нему состояния hover и focus, чтобы дать понять посетителям, что на него можно нажать:

Вам не нужен для этого JavaScript - 11

Я не буду здесь вдаваться в обсуждения темы «только у ссылок должны быть курсоры-указатели», самое главное — показать, что можно что-то сделать.

▍ Модальные диалоговые окна

Иногда вам нужно сообщить пользователю о чём-то, попросить его о чём-то или что-то подтвердить. В JavaScript для этого нужны alert(), prompt() и confirm(). Но у них есть довольно большой недостаток: они блокируют основной поток, то есть ваша страница не может делать ничего другого. Кроме того, они нативны для браузера, так что их нельзя стилизовать, чтобы работать со своим дизайном.

Создание собственного диалогового окна ещё и может вызвать проблемы: для обеспечения accessibility нужно удерживать фокус внутри диалогового окна, объявлять о его модальности и гарантировать, что пользователи случайно не вышли из него; к тому же вам придётся бороться с каким-нибудь чат-виджетом, захватившим z-индекс 2147483647 (кто знает, тот понял).

Поэтому теперь в браузерах есть более нативный элемент диалогового окна:

<dialog>
  <form method="dialog">
    <h3><font color="#000">▍ This is a pretty dialog</font></h3>
    <button type="submit">Close</button>
  </form>
</dialog>

Этот элемент по умолчанию не отображается, поэтому здесь я немного сжульничаю и воспользуюсь Javascript:

document.querySelector("button").addEventListener("click", () => {
  document.querySelector("dialog").showModal();
});

Вам не нужен для этого JavaScript - 12

Сейчас ведутся работы над тем, чтобы можно было открывать диалоговые окна без JavaScript, но пока у них даже нет полных спецификаций, не говоря уже о реализациях. Так что пока нам нужно использовать JavaScript для открытия диалогового окна. Но на этом всё, в остальном это нативный HTML и CSS.

Элемент dialog имеет функцию showModal(), с помощью которой можно открыть диалоговое окно. Это диалоговое окно открывается в слое top-layer, который является новой концепцией для браузеров. Объяснение можно прочитать в MDN: Top layer.

Top layer — это новый слой, отдельный от вашего HTML, на который можно «поднимать» элементы. Это означает, что элементы на top layer всегда будут находиться поверх всего, вне зависимости от z-индекса элемента и вложенности контекста наполнения.

Однако когда он открыт, можно заметить, что браузер не предоставляет никакого UI. По сути, диалоговое окно — это div (не кнопка!), и вы сами должны предоставить UI для его закрытия. Именно это и делает форма в приведённом выше коде. Можно заметить, что она имеет метод «dialog». При отправке этой формы браузер воспринимает это как сигнал для повторного закрытия диалогового окна.

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

<dialog>
  <form method="dialog">
    <p>Tabs or spaces?</p>
    <button type="submit" value="wrong">Tabs</button>
    <button type="submit" value="correct">Spaces</button>
  </form>
</dialog>

Кнопку, которую нажал пользователь, можно узнать, прослушивая событие close dialog и считывая его свойство «returnValue»:

dialog.addEventListener("close", function () {
  console.log(dialog.returnValue);
});

Вам не нужен для этого JavaScript - 13

Если присутствуют какие-то другие данные формы, то можно их считать как formData.

Так как с точки зрения стилизации диалоговое окно, по сути, является div, можно стилизовать его любым удобным образом. Браузер автоматически поместит его в середину экрана, но всё остальное придётся делать вам.

Также Dialog имеет новый псевдоэлемент ::backdrop. Это слой, находящийся между диалоговым окном и остальной частью страницы, и его можно стилизовать, например, размывая страницу или привлекая внимание пользователей к диалоговому окну иным образом. Например, можно наложить белый слой и размыть страницу:

dialog::backdrop {
  background: #fff5;
  backdrop-filter: blur(4px);
}

Вам не нужен для этого JavaScript - 14

Как и сам элемент диалогового окна, фон позиционируется браузером, поэтому вам не придётся беспокоиться о скроллинге, фиксированных элементах и изменении окна браузера. Обработкой всего этого занимается сам браузер.

▍ В заключение

Надеюсь, в статье вы нашли информацию, которая помогла понять, что в своих будущих проектах можно использовать чуть меньше JavaScript. При внесении изменений в проверенные практикой реализации всегда стоит их тестировать, чтобы обеспечить удобный доступ для всех пользователей.

Я мог бы добавить в эту статью ещё десятки примеров, вот некоторые из них:

  • Нативный плавный скроллинг при помощи scroll-behavior: smooth (но только когда этому соответствует prefers-reduced-motion: no preference!).
  • Нативные карусели с автоматическим выравниванием скроллинга.
  • Элементы «In-view» с position: sticky.
  • …Не говоря уже о целой концепции CSS container queries.

А в будущем появится множество других крутых штук:

  • Анимации, привязанные к скроллингу.
  • Выстраивание в сетки не через masonry.js, а при помощи grid-template-rows: masonry.
  • Полностью стилизуемый select с новым элементом selectlist (благодаря которому можно стилизовать каждую часть select без уничтожения всей имеющейся у него нативной функциональности).
  • Селектор :has(), который позволит избавиться от целого класса функций выбора на JS.

Эта статья — адаптированный доклад с конференции, в котором я подробнее рассказываю об этой и другой темах. Доклад можно посмотреть здесь: Stop Using JavaScript for That: Moving Features from JS to CSS and HTML.

В конце я хочу ещё раз повторить основную мысль статьи:

Даже если вы знаете, что для чего-то нужен JavaScript, это не значит, что он нужен по-прежнему. Можно делать более качественные веб-сайты, время от времени проверяя свои допущения.

Автор:
ru_vds

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js