<divelopers> – Telegram
<divelopers>
1.03K subscribers
21 photos
253 links
Рандомные мысли про HTML, CSS, доступность, пользовательские интерфейсы, производительность, браузеры и веб-стандарты.

Автор: @alexnozer
Download Telegram
You don't know HTML: loading

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

Для этого применяются специальные библиотеки. Они полагаются на Intersection Observer, чтобы отслеживать пересечения изображений с областью просмотра. Затем ссылки из атрибутов data-src и data-srcset подставляются в src и srcset, что инициирует загрузку.

С библиотеками есть проблемы:

- Зависимость от JS и дополнительные накладные расходы. Ленивая загрузка не будет работать, пока не выполнится код библиотеки. Поэтому ссылки помещают в data-* атрибуты;

- Поисковые роботы могут не извлекать ссылки из data-src/data-srcset или игнорировать изображения без src. Таким образом можно навредить SEO;

- Атрибут src обязательный, поэтому многие инструменты анализа разметки (валидаторы, линтеры, сканеры доступности) будут игнорировать такие изображения или помечать как невалидные;

- Для предотвращения проблем с JS и SEO нужно добавлять дополнительный элемент <img> и вкладывать его в <nonoscript>.

Проблемы решает встроенный в HTML атрибут loading. Значение eager сообщает, что ресурс нужно обрабатывать как обычно. Это значение по умолчанию, поэтому атрибут можно не указывать. Значение lazy включает встроенную в браузеры ленивую загрузку.

Атрибут доступен у элементов <img> и <iframe>, также есть предложение добавить его для <video>. При установке loading="lazy" ресурс не будет скачиваться на этапе обработки HTML, сканер предварительной загрузки не добавит ссылку в очередь.

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

Можно было бы сказать, что для всех <img> и <iframe> нужно установить loading="lazy", но это не так. Не стоит устанавливать loading="lazy" для элементов в верхней части страницы, которые видны сразу.

Изображение может стать кандидатом на LCP (Largest Contentful Paint). В таком случае его важно показать как можно скорее. loading="lazy" помешает заранее начать загрузку. В результате пользователи получат изображение позже и значение метрики LCP возрастёт.

#ydkhtml #performance
👍183💯3👌2
Slick slider

Помните библиотеку для создания каруселей Slick? На старте карьеры я её использовал, потом перешёл на Swiper, а сейчас предпочитаю лёгкие веб-компоненты или решения на основе Scroll Snap. Работая над улучшением доступности проекта, я вновь столкнулся с Slick.

На сайте заявляется:

Fully accessible with arrow key navigation


В настройках есть параметр accessibility со значением true по умолчанию. Slick кое что делает для улучшения доступности: устанавливает атрибуты, добавляет управление стрелками. К сожалению, этого не достаточно, при аудите я обнаружил много проблем. В целом Slick не соответствует рекомендациям от WAI ARIA.

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

A fully accessible, WCAG 2.0 / 2.1 compliant, drop-in replacement for Slick Slider (1.8.1) intended to make life easier for real-world dev teams who need to pass accessibility audits.


Сколько бы времени мне это сэкономило, если бы я знал… Это полноценная замена Slick версии 1.8.1, в ядре которой исправлены проблемы с доступностью. API и функциональность остались прежними. Если на проекте используется Slick 1.8.1 и нужно сделать его доступным, можно просто заменить файл скрипта на форк.

А вообще удалите Slick и больше никогда его не используйте. Последняя версия вышла 7 лет назад, на GitHub 1210 открытых issue, проект больше не поддерживается и не развивается. Ещё и jQuery требует для работы. Это касается и React-обёрток над Slick, так в пакетах оказывается jQuery.

Поговорите с дизайнером, действительно ли вам нужны карусели. Если нужны, отдавайте предпочтение лёгким и современным вариантам. Swiper предлагает веб-компонент, совместимый с любым стеком. Для простых случаев рассмотрите sl-carousel, Splide, Scroll Snap Slider, Snap Slider. Со временем можно будет перейти на CSS-карусели.

#js #a11y
🔥23👍9👎1
Видео с MinskJS #12

Опубликован плейлист докладов с MinskJS #12. Запись прямой трансляции разрезана на отдельные видео для удобного просмотра.

Я выступал с темой «да кто такие эти ваши веб-компоненты?!» и попытался объяснить, что такое веб-компоненты, сравнил их с фреймворками и показал, где они могут пригодиться.

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

Настя Котова объяснила, что такое node-gyp, зачем он нужен, почему при установке зависимостей и запуске сборки может упасть ошибка gyp ERR! и как это можно исправить.

Если вы хотите выступить с докладом на тему фронтенда, но сомневаетесь — смело подавайте заявку на MinskCSS или MinskJS. Организаторы рады новым спикерам и помогут с подготовкой доклада.

#js
👍63🔥3
Разметка бокового фильтра

Представьте классическую страницу каталога товаров в интернет-магазине. В центре список карточек в виде плитки, а сбоку фильтр для уточнения параметров. Я задумался, как лучше разметить структуру такой страницы. В частности, как разметить блок с фильтром. Хочется, чтобы это был чётко выделенный участок страницы с возможностью быстрого перехода и понятной семантикой. На ум пришло 6 вариантов.

<aside>

Если опустить мантру «<aside> для боковых колонок», то этот элемент предназначен для контента, который косвенно связан с окружающим его контентом и который можно удалить без потери смысла. Боковой фильтр подходит под эти критерии. К тому же <aside> — ориентир и к нему можно быстро перейти.

<nav>

<nav> группирует элементы, которые используются для навигации. Фильтр можно рассматривать как своего рода навигацию по каталогу товаров. Хороший фильтр дополняет адрес страницы параметрами, чтобы можно было сохранить ссылку или поделиться с кем-то. <nav> тоже ориентир и позволяет быстро перейти к фильтру.

<search>

<search> предназначен для разметки набора элементов, которые предоставляют функции поиска или фильтрации. Уже по описанию элемент подходит. Как и другие элементы, <search> — ориентир с возможностью быстрого доступа. Остаётся только вопрос с браузерной поддержкой, так как элемент относительно новый.

<form>

Хороший фильтр дополняет адрес страницы параметрами. Самый простой способ добиться этого — использовать <form>. Если элементы фильтра находятся внутри <form> этого может быть достаточно и другие семантические обёртки не нужны. <form> не ориентир, но быстрый доступ возможен через список форм. Или можно связать заголовок и форму, тогда получится ориентир.

<section>

Можно не вдаваться в нюансы семантики <aside>, <search>, <nav> и <form>, а просто использовать <section>. Элемент группирует связанные части страницы без дополнительной смысловой нагрузки. Сам по себе <section> ничего не даёт, но с привязанным заголовком он становится ориентиром. С помощью aria-roledenoscription можно задать произвольное название роли.

<div role="group"> или <fieldset> + <legend>

Последний вариант — просто сгруппировать элементы фильтра без добавления дополнительной семантики и создания ориентира. Для этого можно использовать <div role="group">. Или, следуя первому правилу ARIA — завернуть всё в <fieldset> + <legend>, что создаст группу с заголовком. Для большей понятности можно добавить описание роли.

#html #a11y
😱6🔥31👍1
Какой вариант разметки области с боковым фильтром вы выберите?
Final Results
54%
<aside>
6%
<nav>
7%
<search>
6%
<form>
2%
<section>
1%
<div role="group">
3%
<fieldset> + <legend>
4%
<div class="filter">
16%
Смотреть ответы
🌚12
Проверка email

Бывает необходимость проверить email на валидность. Я видел всякое: простую проверку на наличие @ в строке, самописные и взятые со StackOverflow регулярные выражения и даже специализированные библиотеки. Есть более простой способ:

function isEmailValid(email) {
const input = document.createElement('input');
input.type = 'email';
input.value = email;

return input.checkValidity();
}

console.log(isEmailValid('foo@bar.baz'));
// true

console.log(isEmailValid('invalid'));
// false


Во всех браузерах есть <input type="email">. У него под капотом встроена валидация. Зная этот факт, можно создать <input>, установить значение и вызвать метод checkValidity(). Он вернёт true, если email валидный и false в противном случае. При этом не обязательно добавлять поле на страницу.

В спецификации HTML описано, как происходит валидация email-адресов. Там учитываются международные стандарты RFC 5322 и RFC 1034. Ещё одна особенность встроенной валидации в том, что можно проверять одновременно несколько email. Строка будет считаться валидной, если все email, разделённые запятой, будут валидны. Для этого у <input> должен быть установлен атрибут или свойство multiple.

function isEmailValid(email) {
const input = document.createElement('input');
input.type = 'email';
input.multiple = true;
input.value = email;

return input.checkValidity();
}

console.log(isEmailValid('foo@bar.baz'));
// true

console.log(isEmailValid('foo@bar.baz,baz@foo.bar'));
// true

console.log(isEmailValid('foo@bar.baz,invalid'));
// false


Можно доработать функцию, чтобы она работала с массивом email-адресов вместо строки, обрезала лишние пробелы и склеивала всё в строку с запятыми.

#js
🔥59👍4🤔3🤝2
16 ошибок производительности (часть 1)

Пол Кальвано выступил с докладом на конференции performance.now(), в котором рассказал о 16-ти распространённых ошибках производительности. На основе базы данных HTTP Archive (~16 миллионов сайтов) он показал, как часто встречаются эти ошибки. Хороший доклад, рекомендую посмотреть. А я поделюсь списком ошибок с кратким описанием. Пост будет разбит на 3 части, чтобы не делать его слишком большим.

1. Ленивая загрузка LCP-изображения

<img src="hero.jpg" loading="lazy" alt="">


loading="lazy" скрывает изображение от сканера предварительной загрузки. Браузеру нужно знать, находится ли изображение в области просмотра, для чего нужно дождаться загрузки всех стилей. LCP-изображение должно загрузиться как можно быстрее. Ленивая загрузка препятствует этому.

Как исправить: удалить атрибут loading у LCP-изображения или установить значение loading="eager".

2. Ленивая загрузка приоритетных изображений

<img src="hero.jpg" loading="lazy" fetchpriority="high" alt="">


Атрибут fetchpriority="high" увеличивает приоритет загрузки, что нужно для важных изображений. loading="lazy", напротив, откладывает загрузку изображений. В итоге изображения будут загружаться с высоким приоритетом, но только после загрузки всех стилей и при попадании в область просмотра.

Как исправить: удалить атрибут loading у изображений с атрибутом fetchpriority="high" или установить значение eager или удалить атрибут fetchpriority, если изображение должно загружаться лениво.

Асинхронное декодирование приоритетных изображений

Изначально изображения с атрибутами fetchpriority="high" и decoding="async" в докладе Пола были отмечены как ошибка. Позже слайды были исправлены, в целом это не ошибка.

3. Предварительное соединение без crossorigin

<link rel="preconnect" href="https://example.com">


<link rel="preconnect"> заранее устанавливает соединение с указанным доменом. Без атрибута crossorigin запросы к сторонним доменам блокируются CORS-политикой браузера. Время на инициализацию соединения будет потрачено зря, а соединение в итоге будет прервано.

Как исправить: добавить к <link rel="preconnect"> атрибут crossorigin.

4. Предварительная загрузка нескольких изображений

<link rel="preload" href="image@x1.jpg" as="image">
<link rel="preload" href="image@x2.jpg" as="image">


<link rel="preload"> предварительно загружает указанный ресурс с высоким приоритетом. Если указать несколько изображений разных размеров, браузер скачает их, но не все из них будут использованы.

Как исправить: использовать <link rel="preload"> с атрибутами imagesrcset и imagesizes, убедиться, что значения соответствуют атрибутам srcset и sizes у изображений.

5. Предварительная загрузка большого объёма данных

<link rel="preload" href="heavy_noscript.js" as="noscript">


Загрузка тяжёлых ресурсов требует времени. <link rel="preload"> перемещает ресурсы в начало очереди загрузки, из-за чего другие важные ресурсы опускаются ниже. Тяжёлый ресурс займёт соединение на какое-то время, а важные ресурсы от этого загрузятся позже.

Как исправить: удалить <link rel="preload"> с тяжёлыми ресурсами и отложить их загрузку, если они не критичны для страницы.

#html #performance
👍16🔥52
16 ошибок производительности (часть 2)

Продолжение предыдущего поста с обзором ошибок производительности из доклада Пола Кальвано.

6. Предварительная загрузка видео

<link rel="preload" href="video.mp4" as="video">


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

Как исправить: удалить <link rel="preload" href="..." as="video">.

7. Предварительная загрузка неиспользуемых ресурсов

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

Как исправить: проверить страницы с <link rel="preload"> на предмет ошибки в консоли, которая сообщает о предварительной загрузке неиспользуемых ресурсов, удалить указанные там <link rel="preload">.

8. Предварительная загрузка без as

<link rel="preload" href="font.woff2">


Атрибут as сообщает браузеру тип ресурса, чтобы он понимал, что дальше с ним делать. Без атрибута as предварительная загрузка ресурса не инициируется.

Как исправить: добавить атрибут as к <link rel="preload"> с соответствующим типом ресурса.

9. Предварительная загрузка разных форматов шрифтов

<link rel="preload" href="font.ttf" as="font">
<link rel="preload" href="font.eot" as="font">
<link rel="preload" href="font.noscript" as="font">
<link rel="preload" href="font.woff" as="font">
<link rel="preload" href="font.woff2" as="font">


Браузер использует наиболее подходящий формат шрифта, который он умеет обрабатывать. Все современные браузеры понимают woff2. Остальные шрифты загрузятся просто так и не будут использованы.

Как исправить: удалить <link rel="preload"> для всех форматов шрифтов, кроме woff2.

10. font-display: swap для предварительно загруженных шрифтов

<link rel="preload" href="font.woff2" as="font">
<style>
@font-face {
/* ... */
src: url("font.woff2") format("woff2");
font-display: swap;
}
</style>


font-display: swap говорит браузеру, что можно сразу отображать текст системным шрифтом, пока пользовательский не загрузился. Когда загрузка шрифта завершится, нужно заменить системный шрифт на пользовательский. Это что-то вроде ленивой загрузки. <link rel="preload"> загружает шрифт заранее с высоким приоритетом. Возникает конфликт приоритетов.

Как исправить: использовать или <link rel="preload">, или font-display: swap.

#html #performance
👍10🔥2😱2🌚1
16 ошибок производительности (часть 3)

Заключительная часть обзора ошибок производительности из доклада Пола Кальвано.

11. Нет сжатия Gzip или Brotli

Сжатие уменьшает размер текстовых ресурсов (HTML, CSS, JS, SVG, JSON и других), что сокращает время их загрузки. Эта оптимизация отсутствует на 10% сайтов по данным HTTP Archive.

Как исправить: включить на сервере gzip, brotli или ztstandard и настроить соответствующие параметры.

12. Низкий уровень сжатия

У сжатия бывают разные уровни. На серверах nginx уровень сжатия (gzip_comp_level) по умолчанию 1, если не указано иное. При таком уровне размер сжатого файла может быть больше исходного.

Как исправить: проверить настройки сжатия и установить более высокие значения в зависимости от возможностей сервера.

13. Сторонние ресурсы не сжаты

Ресурсы сторонних поставщиков (виджеты, маркетинговые скрипты, аналитика, реклама, чат-боты) могут поставляться без сжатия.

Как исправить: связаться с поставщиком и попросить настроить сжатие или разместить ресурсы на собственных серверах/прокси с настроенным сжатием.

14. Обход оптимизации изображений

Иногда для оптимизации изображений используются специальные сервисы и CDN. В URL передаются настройки, которые применяет сервис и возвращает обработанное изображение. Неверные настройки могут привести к загрузке неоптимальных изображений.

Как исправить: проверить URL изображений и убедиться, что переданы нужные настройки.

15. Использование S3 для доставки изображений

S3 от Amazon — это популярное хранилище статических ресурсов. Оно не работает как CDN и не применяет оптимизации к изображениям.

Как исправить: перенести изображения на специализированный для этого CDN.

16. Использование gzip для сжатия изображений

Сжатие gzip подходит для текстовых файлов, так как работает на основе повторяющихся фрагментов текста. Изображения — бинарный формат, они сжимаются иначе. Текстовое сжатие не даст преимуществ в размере бинарных файлов, но потребует время на кодирование/декодирование.

Как исправить: отключить текстовое сжатие для растровых форматов изображений и бинарных файлов.

#performance
🔥8👍32
Ориентиры

Ориентиры (landmarks) — это термин из спецификации ARIA, который описывает важные области страницы с возможностью быстрого доступа. Такие области содержат контент и элементы управления, которые служат определённой цели. Использование ориентиров делает навигацию по странице понятной, предсказуемой и помогает удовлетворить критерии WCAG.

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

К ориентирам относятся следующие роли ARIA:

- banner, аналог в HTML — <header>, который не вложен в <article>, <aside>, <main>, <nav>, <section> или элементы с соответствующими ролями. Область страницы, которая повторяется на других страницах и содержит навигацию, логотип, поиск, переключатель языка, форму входа в личный кабинет и так далее:

- complementary, аналог в HTML — <aside>. Область страницы с вспомогательным контентом, который дополняет окружающий контент и который можно убрать без потери смысла основного контента;

- contentinfo, аналог в HTML — <footer>, который не вложен в <article>, <aside>, <main>, <nav>, <section> или элементы с соответствующими ролями. Область страницы, которая повторяется на других страницах и содержит дополнительную навигацию, контактные данные, авторские права, юридическую и справочную информацию;

- form, аналог в HTML — <form> с заданным именем. Область страницы с набором полей для ввода данных и отправки их на сервер для дальнейшей обработки;

- main, аналог в HTML — первый <main> на странице. Область страницы с основным контентом, ради которого пришёл пользователь;

- navigation, аналог в HTML — <nav>. Область страницы с навигационными элементами как по всему сайту, так и по текущей странице;

- search, аналог в HTML — <search>. Область страницы, которая предлагает элементы управления для поиска или фильтрации результатов;

- region, аналог в HTML — <section> с заданным именем. Область страницы с произвольным контентом, который не попадает под другие ориентиры и который считается важным, чтобы быть в списке ориентиров;

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

#html #aria #a11y
👍6🔥21
You don't know HTML: hidden

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

<div hidden>
<!-- ... -->
</div>

<div hidden="hidden">
  <!-- ... -->
</div>


Элементы скрываются по простой причине: атрибут hidden реализован в браузерах как презентационный и к нему применяется соответствующее правило.

[hidden] {
display: none;
}


Если в стилях разработчика для элемента не указано свойство display, то при добавлении атрибута hidden он будет скрыт. Если значение display в стилях разработчика указано, то оно переопределяет правило атрибута hidden, так как по каскаду стили разработчика переопределяют стили браузера.

Есть два способа предотвратить неожиданное поведение «атрибут есть, а элемент не скрыт». Первый — добавить в глобальные стили правило с !important. Это тот редкий случай, когда использование !important уместно:

[hidden] {
  display: none !important;
}


Второй способ — добавить правило для [hidden] у элементов, где планируется использовать атрибут. Можно сочетать с :where() для сохранения специфичности:

.element {
display: flex;
}

.element:where([hidden]) {
display: none;
}


Атрибут hidden отражается в свойство hidden и наоборот. Поэтому в JS можно удобно переключать состояние видимости и не беспокоиться о значении свойства display.

// управление через атрибут
element.setAttribute('hidden', '');
element.removeAttribute('hidden');

// управление через свойство
element.hidden = true;
element.hidden = false;

// проверка, скрыт ли элемент
if (element.hidden) {
//...
}

// переключение
element.hidden = !element.hidden;


На мой взгляд это удобнее, чем работать с инлайн-стилями или делать специальный атомарный класс.

// управление через свойство display
element.style.display = 'none';
// нужно помнить предыдущее состояние
element.style.display = 'flex';

// управление через атрибут style
element.setAttribute('style', 'display: none');
// можно удалить другие нужные инлайн-стили
element.removeAttribute('style');

// управление через утилитарный класс
element.classList.add('is-hidden');
element.classList.remove('is-hidden');


Из-за того, что у браузеров есть стили для hidden, элемент можно скрыть изначально на уровне разметки. Не нужно ждать загрузки, обработки и применения стилей. Поэтому hidden может стать решением в тех ситуациях, когда при загрузке страницы скрытый контент на доли секунд виден из-за того, что стили не догрузились.

Ещё одна особенность hidden в том, что атрибут может принимать значения. То есть это одновременно булев и перечисляемый атрибут. Так сложилось исторически из-за обратной совместимости. Атрибут без значения, со значением в виде пустой строки, строки hidden и строки с не валидным значением трактуется браузером как если у элемента установлен атрибут без значения.

<!-- варианты эквивалентны -->
<div hidden></div>
<div hidden=""></div>
<div hidden="hidden"></div>
<div hidden="invalid"></div>


Но если указать в качестве значения until-found, поведение меняется. hidden="until-found" делает элемент скрытым до тех пор пока пользователь не воспользуется функцией поиска по странице. Если внутри элемента есть контент, попадающий под условия поиска, то элемент будет показан. Также отличается значение в стилях браузера.

[hidden="until-found"] {
content-visibility: hidden;
}


Эта особенность полезна при разработке компонентов, которые скрывают контент, например раскрывающиеся блоки или аккордеоны. Есть статья с примером реализации аккордеонов и использованием hidden="until-found".

#ydkhtml
18👍7🔥6
Menu elements

Дисклеймер: это обзор раннего предложения новых функций веб-платформы. Синтаксис может измениться в будущем или от функции могут отказаться.


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

Для реализации навигационного меню в HTML есть элементы <nav>, <ol>, <ul>, <li> и <a>. Для реализации меню команд в HTML ничего нет, нужно использовать ARIA. Есть элемент <menu>, который по определению предназначен для команд. Но он просто заменяет список <ul> и не добавляет функциональности и нужной семантики.

В OpenUI появилось предложение по добавлению в HTML новых элементов для создания меню команд.

<menubar> — контейнер для элементов меню с встроенной ролью menubar. Предназначен для создания основного меню приложения. Обычно меню располагается в верхней части интерфейса, отображается всегда, содержит горизонтальный список опций, которые раскрывают выпадающие подменю.

<menulist> — контейнер для элементов меню с встроенной ролью menu. Предназначен для создания выпадающих меню и контекстных меню. Пункты меню, как правило, вызывают действия в приложении или раскрывают дополнительные уровни меню.

<menuitem> — элемент меню, аналог кнопки для вызова действий или переключения подменю. Поддерживает атрибут disabled для отключения и псевдо-класс :disabled для стилизации. Может открывать подменю, если указан атрибут menu с идентификатором, как popover. Генерирует событие toggle, если переключает подменю.

<menuitemcheckbox> — элемент меню, который работает как флажок. Подразумевает группу из нескольких связанных элементов для выбора дополняющих опций. Например, список отображаемых колонок таблицы или список активных панелей. Для группировки предлагается использовать элемент <fieldset>.

Меню-флажок поддерживает псевдо-класс :checked для стилизации выбранного состояния и псевдо-элемент ::checkmark для стилизации галочки. При нажатии генерирует события change и toggle. Может быть отключён атрибутом disabled и стилизован псевдо-классом :disabled.

<menuitemradio> — элемент меню, который работает как радио-кнопка. В основном работает как <menuitemcheckbox>, по предназначен для выбора взаимоисключающих опций. Например, режим сортировки или отображения данных. Поддерживает те же псевдо-классы, псевдо-элементы, атрибуты и события, что и <menuitemcheckbox>.

Эти элементы в сочетании с Popover API, Focusgroup и Anchor Positioning позволят создавать меню команд как в рекомендациях ARIA, но без использования ARIA-атрибутов для передачи семантики и JS для реализации поведения клавиатуры и отображения выпадающих меню. Сами действия, вызываемые элементами меню, само собой, нужно определить в JS.

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

#html #ui
8👍3🔥3
CSS-хаки и здравый смысл

Обозревая новые функции CSS, я часто упоминаю, что они позволяют реализовать тот или иной интерфейс без лишнего JS. Например, карусели, select или аккордеоны. Иногда подход «без лишнего JS» доходит до крайностей, когда придумывают так называемые Pure CSS решения с хаками для замены JS.

В HTML и CSS есть ряд особенностей, которые можно использовать не по прямому назначению и добиваться интересных результатов:

- флажки и радио-кнопоки в сочетании с псевдо-классом :checked и комбинаторами соседства;
- якорные ссылки в сочетании с псевдо-классом :target и элементами с атрибутом id;
- псевдо-классы :hover и :focus в сочетании с комбинаторами соседства.

На основе этого делают вкладки, карусели, аккордеоны, раскрывающиеся меню, диалоги, галереи и прочие компоненты. Есть сайт с примерами.

Я и сам когда-то делал дровер через флажок, многоуровневое меню на ховерах и прочее. Был страшно собой доволен, ведь я сделал работающий компонент без JS, круто! Сейчас я понимаю, что это не правильно.

Нужно различать CSS-хаки и реальные решения для интерфейcов на CSS. Флажок — это элемент формы для выбора опции, а не переключатель. Якорная ссылка — это способ навигации по странице, а не решение для вкладок и диалогов.

CSS-хаки изящны, но портят пользовательский опыт и доступность. Неочевидные возможности, сложные селекторы и хитрые свойства усложняют поддержку проекта. Хаки полагаются на структуру HTML, что делает решения хрупкими. Хорошо эту тему раскрыл Вадим Макеев в старом докладе «Чистый CSS для грязных трюков».

Но есть хорошие примеры использования HTML и CSS для реализации интерфейса. Предлагаю доклад Никиты Дубко и доклад Килиана Валкхофа. Оба про то, как с умом использовать современные возможности HTML и CSS для решения классических задач без JS и вреда пользовательскому опыту.

#html #css #ui
👍173🔥3
Актуальные фавиконки

Самый простой способ добавить фавиконку на сайт — поместить файл favicon.ico в корень. Старый и железобетонный способ, который работает везде.

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

Проблема генераторов в том, что они отдают архив с десятком фавиконок в разных форматах и размерах, а также полотно кода, который нужно вставить в <head> для подключения. Это избыточно. Сегодня достаточно трёх файлов:

- /favicon.ico
- /icon.noscript
- /apple-touch-icon.png

Если сайт должен устанавливаться и работать как PWA, то к этому нужно добавить ещё четыре файла:

- /manifest.webmanifest
- /icon-192.png
- /icon-mask.png
- /icon-512.png

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

Не тащите в проекты гору устаревших фавиконок и не захламляйте <head> лишними ссылками на эти файлы.

#html
21👍11
Предположения по производительности

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

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

Одна из проблем — неверные предположения. Разработчики запускают аудит, видят плохие показатели, смотрят на список проблем и выборочно их исправляют. Важно приоритизировать проблемы и делать упор на те, которые оказывают наибольшее влияние.

Нет смысла конвертировать шрифты и изображения в современные форматы, добавлять ленивую загрузку и проставлять атрибуты width и height, если время получения первого байта (TTFB) составляет 1.5 секунды. В первую очередь нужно разбираться с сервером или с сетью.

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

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

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

Ещё оптимизации могут стрелять в ноги. preload ускоряет загрузку критического ресурса, но откладывает другие. Ленивая загрузка полезна, но не в начальной области просмотра. CDN сокращает расстояние до пользователя, но добавляет соединение к стороннему домену.

Улучшения в одном месте могут привести к ухудшениям в другом. Перед началом работ по улучшению производительности стоит делать контрольные замеры, вносить точечные изменения и делать повторные замеры для сравнения. Для итераций можно использовать Lighthouse в DevTools не смотря на то, что он не показывает реальную картину.

#performance
👍11🔥21
Разметка бокового фильтра: итоги

Не так давно я делал опрос, какой элемент использовать для разметки области с фильтром в интернет-магазине.

Самым популярным выбором, предсказуемо, стал <aside>. Он закрепился в массовом сознании как элемент для боковых колонок. Фильтр в интернет-магазинах чаще всего отображён в виде боковой панели, поэтому <aside>. Семантика элемента, в принципе, подходит для фильтра, но об этом в отдельном посте.

Вариант <div role="group"> я бы исключил. Во-первых, есть встроенные элементы <fieldset> и <legend>, которые создают именованную группу для набора полей. Во-вторых — это группировка без семантической нагрузки и ориентира. На мой взгляд фильтр достаточно важен и его стоит выделить в ориентир.

<section> тоже выглядит сомнительно. Он подходит для разметки разделов, для которых нет более подходящих семантических ориентиров. Это подойдёт для структуризации страницы с большим количеством разделов. Для фильтра есть более подходящие варианты.

Если рассматривать фильтр как способ навигации по каталогу, может подойти <nav>. Но в классическом понимании навигация — это набор ссылок на другие страницы, а не набор элементов управления. Поэтому <nav> я бы тоже исключил из списка вариантов.

Фильтр обычно состоит из групп флажков, радио-кнопок, ползунков и полей ввода для цен. Мне подсознательно хочется использовать для этого <form>. Плюсом Form API и прогрессивное улучшение от классической отправки формы с перезагрузкой до AJAX.

Сам по себе элемент <form> не ориентир, пока у него нет имени. Это легко исправляется атрибутами aria-labelledby или aria-label. Формы, как мне кажется, уже достаточно и оборачивать фильтр чем-то ещё будет избыточно.

Есть элемент <search>, который по описанию прямо предназначен для разметки поиска или фильтрации. Кажется, на нём и нужно остановить выбор. Но если фильтр уже размечен через <form> с именем?

Я пришёл к двум решениям. Первое — совместить форму с семантикой <search>. У элемента <form> согласно спецификации ARIA in HTML можно указывать роль search. Второе — не добавлять форме имя и не превращать её в ориентир, а обернуть элементом <search>.

<!-- 1 -->
<form
action="/catalog"
method="get"
autocomplete="off"
novalidate
role="search"
aria-label="Фильтр"
>
<!-- элементы фильтра -->
</form>

<!-- 2 -->
<search aria-label="Фильтр">
<form
action="/catalog"
method="get"
autocomplete="off"
novalidate
>
<!-- элементы фильтра -->
</form>
</search>


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

#html #a11y
15👍8
ARIA Notify

Члены команды разработки Edge анонсировали новую функцию под названием ARIA Notify. Это новый API для вызова оповещений для целей доступности. API пока доступен за origin trial или экспериментальным флагом в Edge 136.

В стандарте ARIA есть концепция «живых регионов» (live regions). Это некоторые области на странице, изменения контента в которых приводят к озвучке этих изменений программами чтения с экрана. Таня Фокина написала подробную статью про «живые регионы» в Доке.

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

Это применяется для:

- динамически изменяющихся данных (изменение цены в зависимости от выбранных опций, количество товаров в корзине, новые сообщения в чате);
- ошибок (неверные данные в форме, ошибка производимой операции);
- уведомлений (новые письма, изменение статуса заявки, всплывающие «тосты»);
- информирования о событиях (переключение слайда в галерее, окончание загрузки).

Работа с «живыми регионами» достаточно сложная. Нужно разобраться в многообразии ролей и состояний для выбора нужной комбинации. Затем проверить интерпретацию в разных программах чтения с экрана. После написать механизм внедрения «живых регионов» на страницу таким образом, чтобы это срабатывало.

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

// Вызов оповещения в контексте документа
document.ariaNotify(
'Иван Иванов присоединился к звонку'
);

// Вызов оповещения в контексте конкретного элемента
document
.querySelector('#text-editor')
.ariaNotify(
'Выделен полужирный текст'
);

// Вызов оповещения с обычным приоритетом
document.ariaNotify(
'Настройки профиля обновлены',
{ priority: 'normal' }
);

// Вызов оповещения с высоким приоритетом
document.ariaNotify(
'Данные не сохранены',
{ priority: 'high' }
);


Метод ariaNotify() вызывается у document или конкретного элемента и генерирует сообщение, как если бы в документ был вставлен «живой регион» или контент существующего был обновлён. Второй аргумент опциональный, принимает объект с единственным пока-что параметром priority.

priority: 'normal' генерирует обычное сообщение. Оно озвучивается после того, как программа чтения с экрана закончит начатую ранее озвучку. С 'high' программа приостановит начатую озвучку и немедленно озвучит сообщение. Похоже, это соответствует механике атрибута aria-live="polite" и aria-live="assertive".

Посмотрим, чем закончится этот эксперимент и что изменится. Вероятно, ARIA Notify заедет в виде стабильного API в одном из ближайших релизов Edge. А так как это Chromium, вслед за Edge функция может появиться в Chrome и других браузерах на этом движке.

#web_api #a11y
😇82👍2🔥1
CSS reading-flow

Вслед за анонсом от Edge, Рейчел Эндрю из команды Chrome поделилась новой функцией для улучшения доступности. Она будет доступна с выходом Chrome 137.

Flexbox и grid позволяют гибко располагать элементы на экране и реализовывать практически любые задумки дизайнеров. Иногда эти задумки выглядят так, что порядок элементов меняется в зависимости от размеров экрана.

Элементы flex-контейнера можно разворачивать значениями row-reverse и column-reverse свойства flex-direction. Порядок конкретных элементов можно менять через свойство order.

В grid-контейнере возможностей больше. Порядок элементов задаётся по номерам или именам grid-линий, именованными областями или как решит браузер (grid-auto-flow).

Эти возможности меняют только визуальный порядок, но не порядок в DOM, что приводит к проблемам с доступностью. Новые CSS-свойства reading-flow и reading-order должны решить эти проблемы.

reading-flow управляет порядком элементов flex и grid-контейнеров, который передаётся дереву доступности. Можно включить соответствие визуальному порядку элементов, а не их порядку в DOM.

reading-order позволяет переопределить порядок конкретного элемента для дерева доступности, подобно тому, как order меняет положение конкретного элемента внутри flex-контейнера.

<style>
.box {
display: flex;
flex-direction: row-reverse;
reading-flow: flex-visual;
}

.box :nth-child(1) {
order: 2;
}
</style>

<div class="box">
<a href="#">1</a>
<a href="#">2</a>
<a href="#">3</a>
</div>

<!--
Визуальный порядок:
1 3 2
-->


Без reading-flow порядок фокусировки ссылок был бы как в DOM: 1, 2, 3. Это не соответствует тому, что отображается на экране. Значение flex-visual передаёт порядок в соответствии с визуальным расположением элементов: 1, 3, 2.

flex-flow передаёт дереву доступности порядок, соответствующий текущему расположению элементов согласно потоку flex-контейнера с учётом row-reverse: 2, 3, 1.

Есть значения grid-rows и grid-columns для передачи порядка по строкам или по столбцами для grid-контейнеров. На специальной странице предлагаются разные примеры использования новых свойств. Там же есть ссылки, где можно оставить обратную связь.

#css #a11y
🔥8👍73🤔3🤓3
Загрузка скрытых изображений

Представьте задачу: нужно показывать разные изображения в зависимости от ширины области просмотра. Это может быть баннер в верхней части страницы с широким изображением для компьютеров и узким для телефонов. Иногда эту задачу решают двумя <img> и служебными классами, которые управляют отображением.

<div class="banner">
<img
src="banner-desktop.webp"
alt=""
width="1200"
height="600"
class="mobile-hide"
>
<img
src="banner-mobile.webp"
alt=""
width="400"
height="200"
class="desktop-hide"
>
<!-- пустой alt, декоративные изображения -->
</div>


Классы mobile-hide и desktop-hide скрывают изображения на мобильных и десктопных экранах. Сами классы могут называться иначе, но суть в том, чтобы скрывать элемент на определённых размерах экрана.

@media (width <= 599px) {
.mobile-hide {
display: none;
}
}

@media (width > 600px) {
.desktop-hide {
display: none;
}
}


По умолчанию изображения с display: none не загружаются. Но если изображение находится в верхней части страницы, которая видна при начальной загрузке, то вопреки display: none оно всё равно будет загружаться. Chrome, например, загружает 6 первых изображений, игнорируя свойство display.

Поэтому правильнее будет использовать элементы <picture> и <source> в сочетании с атрибутом media для выбора источника изображения в зависимости от медиа-запроса.

<picture class="banner">
<source
src="banner-desktop.webp"
media="(width > 600px)"
width="1200"
height="600"
>
<img
src="banner-mobile.webp"
alt=""
width="400"
height="200"
>
</picture>


Также не стоит добавлять изображениям в верхней части страницы атрибут loading="lazy". Ещё можно поэкспериментировать с приоритетом. А ещё <picture> можно использовать как контейнер и задавать ему любые свойства, чтобы не оборачивать в лишний <div>.

#html #performance
🔥26👍4👎21
Группировка классов

Глобальный атрибут class позволяет хранить набор строковых ключей, которые затем можно использовать в селекторах CSS для применения стилей или в JS для поиска элементов.

Согласно спецификации, в class можно указывать произвольные токены, разделённые пробелами. Как таковых, ограничений какими должны быть токены нет. Единственное — синтаксис HTML, где кавычка считается концом значения атрибута.

Поэтому в class можно указывать любые символы. Методология БЭМ использует договорённость по именованию токенов с использованием двойных подчёркиваний и тире __ и --. Tailwind отрывается по полной и использует разные символы как часть собственного специального синтаксиса. Кто-то использует эмодзи в качестве токенов.

Энди Бэлл, автор CSS-методологии CUBE, предлагает использовать квадратные скобки [ ] или вертикальную черту | для группировки классов. Идея в том, чтобы разделить разные классы на группы и записать их в нужном порядке, разделив группы для лучшей читаемости.

<article
class="[ card ] [ section box ] [ bg-base color-primary ]"
data-state="reversed"
></article>

<article
class="card | section box | bg-base color-primary"
data-state="reversed"
></article>


Эту идею можно позаимствовать и использовать вне CUBE. Также для улучшения читаемости можно использовать переносы строк в атрибутах. Можно даже придумать условные обозначения для разных групп классов и использовать в разделителях групп. На этапе сборки всё лишнее можно удалять.

<article
class="
@[ card ]
#[ section box ]
$[ bg-base color-primary ]
"
></article>


#css
🤔16👎14😐5💊4🔥31
14th Global Accessibility Awareness Day

Сегодня, 15 мая, 14й Всемирный День Информирования о Доступности. Цель этой инициативы — больше говорить, думать и узнавать о цифровой доступности и людях с различными нарушениями.

В прошлом году я предлагал посмотреть два видео от WAI и Be My Eyes. Если не видели, хороший повод посмотреть. В этом году рекомендую посты про руководство по созданию доступного контента и обновлённый раздел «как люди с ограничеными возможностями пользуются Интернетом».

По хештегу #a11y@alexnozer_dev можно посмотреть последние публикации канала, затрагивающие тему доступности. Если вы собираетесь внедрять доступность в проект и у вас есть вопросы — вступайте в чат нашего сообщества по доступности. Там можно задать любые вопросы по цифровой и не только доступности.

#a11y
9👍3🔥3