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

Автор: @alexnozer
Download Telegram
You don't know HTML: способы расширения HTML

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

Авторы стандартов и разработчики браузеров внедряют новые элементы (<details>, <dialog>) и улучшают существующие. Но как бы они ни старались, невозможно покрыть всё многообразие контента, сайтов, приложений и способов использования HTML.

Поэтому HTML разработан как расширяемый язык. Спецификация определяет следующие способы безопасного расширения HTML:

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

- атрибуты data-* для данных, которые игнорируются браузером и ни на что не влияют, но могут использоваться для стилизации и передачи данных для скриптов. Довольно часто используются для хранения настроек и мета-данных, которые считываются и используются в JS.

- элемент <meta name="…" content="…"> для добавления мета-данных на уровне страницы. Чаще всего используется для добавления социальной разметки (Open Graph, Twitter Card) для поисковых систем и социальных сетей. Также можно добавлять произвольные данные в виде ключ-значение, а сам элемент <meta> можно размещать не только в <head>.

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

- элемент <noscript type="…"> для добавления произвольных данных в текстовом виде. Значение атрибута type, отличное от text/javanoscript, module и importmap делает <noscript> блоком данных. В нём можно разместить любые данные в текстовом формате, которые затем можно считывать и обрабатывать в скриптах. Это используется микро-форматом JSON-LD.

- модель прототипов JS для расширения существующих API, что делают разные библиотеки. Хотя расширение прототипов считается плохой практикой в мире JS, такой вариант расширения возможен. Главное использовать это с умом, не перезаписывать стандартные свойства и методы, использовать наследование и Symbol.

- атрибуты itemtype, itemscope и itemprop для добавления микро-данных, которые используются другими сайтами и приложениями. Наиболее распространён словарь Schema.org, на который ориентируются поисковые системы и различные парсеры.

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

Стоит отметить ещё некоторые возможности, которые не затронуты стандартом, но также позволяют расширять HTML:

- язык MathML для разметки сложных математических формул
- язык SVG для встраивания векторной графики
- стандарт ARIA для добавления, расширения и переопределения семантики с целью улучшения доступности при использовании вспомогательных технологий

#ydkhtml
👍152
Список описаний на практике

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

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

Первая статья на HTMHell от Дэвида Лура “The underrated <dl> element”. Дэвид приводит три примера. Первый — классический список терминов и определений, для чего <dl> изначально предназначался. Второй пример — блок с цифрами и подписями, такие часто встречаются на сайтах, чтобы подсветить достижения в виде цифр. Последний, третий пример — список характеристик велосипеда в каталоге.

Вторая статья от Бэна Мейерса “On the <dl>”. Бэн приводит четыре примера. Первый — характеристики продукта, в примере это книга. Второй пример — карточка пользователя с контактами и адресом. Третий пример — блок со свойствами в стиле Википедии. Четвёртый пример самый интересный: Бэн показывает большую карточку с информацией о персонаже из Dungeons & Dragons, которая практически полностью состоит из списков описаний.

Списки описаний полезны для группировки связанного контента, который можно представить в виде пар ключ-значение. Если взглянуть на интерфейсы, такие списки можно встретить чаще, чем кажется. Хотя полезность <dl> с точки зрения семантики слегка сомнительная, всё же стоит его использовать для группировки подходящего контента.

#html
🔥18🌚21
Автодополнение номера телефона

Я уже писал про автодополнение в браузерах. Работая над улучшением доступности одного проекта, я настраивал автодополнение в рамках критерия WCAG 1.3.5 Identify Input Purpose. Не углубляясь в детали, критерий требует установки соответствующих цели ввода значений атрибутов type и autocomplete у полей. Одно из них я настроил примерно так:

<label for="ContactPhone">
Phone Number
</label>
<input
type="tel"
name="contact[phone]"
id="ContactPhone"
autocomplete="tel-national"
placeholder="123-555-1212"
>


На вид все хорошо: лейбл есть, type="tel" для ввода телефона есть, autocomplete="tel-national" для телефона в национальном формате (без кода страны, для него рядом другое поле) есть. Проверив этот код через расширение для анализа доступности, я получил ошибку:

The autocomplete attribute has an incorrect value


Значение tel-national описано в спецификации HTML и предназначено для ввода телефона в национальной форме. Баг расширения, подумал я, и завёл issue. Спустя какое-то время я проверил код с этой формой в валидаторе HTML и получил примерно такую же ошибку:

Bad value tel-national for attribute autocomplete on element input: The autofill field name tel-national is not allowed in this context.


Закралось ощущение, что это не баг, а я чего не знаю. В исходом коде теста из расширения есть дополнительная проверка на тип поля. Для type="tel" единственным допустимым значением считается autocomplete="tel", поэтому и возникла ошибка. Но тест же на чём-то основан?

И да, в спецификации HTML есть абзац о том, что значения атрибута autocomplete должны применяться у допустимых типов полей, что отмечено в таблице со значениями. Это логично: спецификация защищает от ситуаций, когда для поля с type="email" устанавливается значение autocomplete="given-name", что не имеет смысла.

Поле с type="tel" предназначено для ввода полного номера телефона, с кодом страны и региона. У такого поля можно установить только autocomplete="tel". Если поле предназначено для ввода части номера (без кода страны и региона), то нужно использовать другое значение autocomplete, а type="tel" для такого уже не подойдёт.

Исправленный вариант:

<label for="ContactPhone">
Phone Number
</label>
<input
type="text"
inputmode="tel"
name="contact[phone]"
id="ContactPhone"
autocomplete="tel-national"
placeholder="123-555-1212"
>


Как было изначально, сохранилось значение autocomplete="tel-national" для номера без кода страны. type="tel" заменён на универсальный type="text" для ввода произвольных данных, который совместим со значением tel-national. И добавлен атрибут inputmode="tel" для возврата виртуальной клавиатуры, удобной для ввода номеров.

#html #a11y
👍29🔥42🤯2
CSS-карусели

Прошло всего несколько месяцев с анонса, а Адам Аргайл поделился новостью, что в 135 версии Chrome появятся встроенные карусели. Их можно будет создавать средствами CSS, с кнопками вперёд/назад, маркерами, плавной прокруткой, перетаскиванием и доступностью.

CSS карусели базируются на Scroll Snap. То есть это блок с горизонтальной и/или вертикальной прокруткой, элементы которого привязываются к заданным точкам. Это поддерживается в браузерах уже достаточно давно.

Есть реализации с дополнительным JS, который добавляет кнопки, маркеры, перетаскивание и прочие возможности. Теперь всё это можно будет добавить с помощью нескольких новых псевдо-элементов: ::scroll-button(), ::scroll-marker и ::scroll-marker-group.

::scroll-button() создаёт кнопки в зависимости от параметра в скобках (top, right, bottom, left и логических аналогов block-start, block-end, inline-start и inline-end). Это полноценные кнопки, которые можно нажимать, фокусировать и стилизовать как нужно.

::scroll-marker создаёт маркеры. Они также интерактивные и стилизуемые. Браузер учитывает количество маркеров относительно количества слайдов и шагов прокрутки. Маркеры синхронизируются с прокруткой, а текущий маркер может быть стилизован через псевдо-класс :target-current.

::scroll-marker-group соответствует контейнеру с маркерами. Его можно размещать в нужной части карусели, ограничить по ширине или ещё как-либо стилизовать. Контейнер предлагает навигацию по маркерам при помощи стрелок.

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

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

Стоит отметить, что расширенные функции прокрутки выходят за рамки каруселей и позволяют реализовать другие интерфейсные паттерны. Есть страница с примерами на базе нового API и запись лайва с обзором разных паттернов от Адама Аргайла. Я в целом против каруселей, но они крайне распространены. Если их можно будет делать без тяжёлых библиотек и со встроенной доступностью — это отлично.

#css
🔥16🤯86
Заголовки и outline

На старте карьеры я запомнил правило: на странице должен быть один <h1>. Остальные заголовки нужно размечать элементами <h2>-<h6> согласно иерархии. Периодически попадаются статьи и комментарии, которые предлагают использовать <h1> для всех заголовков на странице, потому что браузеры и поисковики умеют с этим работать.

Ноги растут из алгоритма outline, который был описан в спецификации HTML. Это алгоритм определения структуры документа, выделения содержания (table of content) и навигации горячими клавишами.

Ещё при разработке XHTML 2.0 вместо заголовков разных уровней предлагалось использовать новый элемент <h>. Уровень должен был автоматически определяться на основе вложенности в элементы секционирования (<section>, <article>, <aside>, <nav>, и другие).

XHTML 2.0 не удался, но некоторые идеи перекочевали в HTML5. В том числе идея использовать <h1> в сочетании с элементами секционирования для разметки заголовков. Долгое время в спецификации можно было найти подобные примеры кода:

<body>
<h1>Основной заголовок</h1>
<section>
<h1>Заголовок раздела 1</h1>
<article>
<h1>Заголовок элемента 1</h1>
</article>
<article>
<h1>Заголовок элемента 2</h1>
</article>
</section>
<aside>
<h1>Заголовок раздела 2</h1>
</aside>
</body>


Что давало такую структуру:

Основной заголовок (ур. 1)
├── Заголовок раздела 1 (ур. 2)
│ ├── Заголовок элемента 1 (ур. 3)
│ └── Заголовок элемента 2 (ур. 3)
└── Заголовок раздела 2 (ур. 2)


Из-за этих примеров стала распространяться информация, что можно использовать только <h1> и не использовать <h2>-<h6>. Из этого следовало, что на странице можно использовать несколько <h1>. Поисковые системы тоже это учитывают. Индексирующему алгоритму, в общем-то, всё равно как размечены заголовки.

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

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

От алгоритма outline решено отказаться. Его следы оставались в спецификации до 1 июля 2022 года. Тогда раздел переписали, удалив описание алгоритма и примеры с <h1>. Термин outline остался в спецификации, но теперь обозначает дерево всех заголовков страницы, которое формируется на основе уровней. В актуальной версии все ещё есть один пример с несколькими <h1> и подписью “A document can contain multiple top-level heading”.

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

Поэтому старое правило «один <h1> на страницу» и соблюдение иерархии заголовков всё ещё актуально. Не стоит экспериментировать, меняя все заголовки на <h1>. Даже если поисковые системы это поддерживают и кто-то говорит, что так можно.

#html
22🔥21🤗3👍21
Миксины в CSS

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


У Chrome, похоже, марафон анонсов новых функций. Адам Аргайл у себя на сайте поделился, что CSS-миксины уже доступны в Chrome Canary за флагом и готовы к экспериментам. К посту приложен вот такой фрагмент кода:

@mixin --box {
aspect-ratio: 1;
inline-size: 100px;
block-size: 100px;
}

.box {
@apply --box;
}


Миксин объявляется через новую директиву @mixin. Затем следует название в синтаксисе <dashed-ident>, как у custom properties. Далее описывается блок с правилами. Применяется миксин через ещё одну новую директиву — @apply. Почти как в SCSS.

Миксины и функции основаны на предложении CSS Mixins & Functions от Мириам Сюзанн, которая много работает над развитием CSS и Sass. Предложение приняли в рабочей группе CSS и на его основе пишется спецификация CSS Functions and Mixins.

Лично для меня миксины — последняя часть пазла, чтобы полностью отказаться от препроцессоров. Я пробовал использовать в проектах чистый CSS и именно миксинов мне не хватало. Где-то это решалось через custom properties и сокращенные свойства, где-то дополнительным классом в HTML.

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

#css
🔥175🥰2💋1
Полностью стилизуемый <select>

Марафон новинок продолжается. В Chrome 135 доступен полностью стилизуемый <select>. Последние несколько лет прорабатывались разные варианты как это сделать. После тестовой реализации и обратной связи от разработчиков, финальный вариант был принят и теперь доступен в Chrome. У нас наконец-то появилась возможность стилизовать стандартный <select>!

Стилизация включается специальным значением свойства appearance:

select, select::picker(select) {
appearance: base-select;
}


Установка свойства меняет режим отрисовки. Вместо системного виджета генерируется новый виджет c HTML-разметкой и базовыми стилями в Shadow DOM. Это позволяет стилизовать внутренние части <select>, но убирает некоторые функции:

- выпадающий список опций не сможет заходить за пределы окна браузера;
- на мобильных устройствах не будет открываться системный интерфейс выбора опций;
- ширина <select> не будет соответствовать самой длинной опции.

Вместе с этим меняется режим парсера. До этого в <select> можно было помещать <option>, <optgroup>, а с недавних пор <hr>. Все остальные элементы игнорируются. В новом режиме внутрь <option> можно поместить изображения, SVG и дополнительную разметку. В сам <select> можно добавить <button> и новый элемент <selectedcontent> для отображения выбранной опции.

<select>
<button>
<selectedcontent></selectedcontent>
</button>
<option>
<noscript aria-hidden>…</noscript>
<span>HTML</span>
</option>
<option>
<noscript aria-hidden>…</noscript>
<span>CSS</span>
</option>
<option>
<noscript aria-hidden>…</noscript>
<span>JavaScript</span>
</option>
</select>


<button>, <selectedcontent>, <option> и его внутренние элементы можно свободно стилизовать как обычные дочерние элементы. Вместе с ними можно стилизовать внутренние части в Shadow DOM, доступ к которым осуществляется через специальные псевдо-элементы и псевдо-классы:

- select:open<select> в открытом состоянии;
- select::picker(select) — выпадающий список опций, реализован как Popover с привязкой через Anchor Positioning;
- select::picker-icon — иконка со стрелкой, которая меняется при открытии, в content можно подставить свой символ или изображение;
- option::checkmark — иконка в виде галочки у выбранной опции, в content можно подставить свой символ или изображение;
- option:checked — выбранная опция.

Можно стилизовать <optgroup>, но не его подпись, создаваемую через атрибут label. К счастью, можно вложить <span> с подписью, стилизовать как нужно и привязать к <optgroup> через ARIA. Доступны псевдо-классы :hover, :focus и :focus-visible, поэтому можно изменять внешний вид <option> при наведении и фокусе. Можно применять flexbox и grid для расположения элементов.

Если браузер не поддерживает новые свойства, то <select> будет работать по-старому. Все недопустимые внутри элементы будут игнорироваться и отображаться в виде обычного текста. Также интересно, что можно отключить appearance: base-select по медиа-запросу, чтобы вернуть на мобильных устройствах системный интерфейс выбора опций.

#css
🔥24🎉6
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