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

Автор: @alexnozer
Download Telegram
Invoker Commands

Я уже делал обзор предложения Invokers API от OpenUI. В конце прошлой недели в блоге Chrome For Developers представили command и commandfor. Invokers API теперь называется Invoker Commands, предложение разделили на две части, первую уже готовят к релизу в Chrome. Пришло время взглянуть на это всё ещё раз.

Invoker Commands — это декларативный механизм вызова команд у указанного элемента при нажатии кнопки. Сейчас для этого нужно написать обработчик, найти целевой элемент и вызывать нужный метод. Предлагается способ делать это в HTML двумя атрибутами: command (ранее invokeraction) и commandfor (ранее invokertarget). Первый принимает название команды, второй — id элемента, у которого нужно вызвать команду.

<button
type="button"
commandfor="hello-dialog"
command="show-modal"
>
Open Dialog
</button>

<dialog id="hello-dialog">
Hello world

<button
type="button"
commandfor="dialog"
command="close"
>
Close
</button>
</dialog>


show-modal и close — это встроенные команды. Если comandfor ссылается на <dialog>, то они соответствуют вызовам методов showModal() и close(). Команды toggle-popover, hide-popover и show-popover соответствуют методам togglePopover(), hidePopover() и showPopover() у элемента с атрибутом popover. Новые атрибуты работают аналогично существующим атрибутам popovertarget и popovertargetaction из Popover API. Планируется, что новые атрибуты со временем заменят их.

В предложении OpenUI было больше встроенных команд. Принято решение разделить предложение на две части и внедрять его постепенно. В первую часть попали команды для работы с <dialog> и popover. Над второй частью ещё работают, но стоит ожидать команд для переключения <details>, раскрытия системных пикеров у <input> и <select>, управления воспроизведением <audio> и <video>, копирования текста, переключения полноэкранного режима и так далее.

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

<button
type="button"
commandfor="custom"
command="--do-something"
>
Do something
</button>

<div id="custom"></div>

<noscript>
const custom = document.querySelector('#custom');
custom.addEventListener('command', (event) => {
if (event.command === '--do-something') {
// ...
}
});
</noscript>


Внешний commandfor не найдёт целевой элемент внутри Shadow DOM так же, как <label for> “не видит” элемент с нужным id внутри Shadow DOM. В будущем эти проблемы будут решены за счёт предложения Reference Target. А пока можно напрямую указать целевой элемент, используя свойство commandForElement.

<my-element>
<template shadowrootmode="open">
<button
type="button"
command="show-popover"
>
Show popover
</button>
<slot></slot>
</template>

<div popover></div>
</my-element>

<noscript>
class MyElement extends HTMLElement {
connectedCallback() {
const popover = this.querySelector('[popover]');
const button = this.shadowRoot.querySelector('button');
button.commandForElement = popover;
}
}

customElements.define('my-element', MyElement);
</noscript>


Invoker Commands вот-вот должны появиться в Chrome. Другие браузеры смотрят на предложение позитивно и тоже займутся внедрением. Затем стоит ожидать дальнейшего развития и добавления новых команд.

#html #web_api
🔥14👍32
Стилизация системных элементов форм

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


Джен Симмонс поделилась в соцсетях, что её команда работает над давней проблемой веб-разработки — стилизацией системных элементов форм.

Проблема постепенно решается. За последние годы появились точечные свойств, такие как accent-color, field-sizing и appearance, которые где-то упрощают стилизацию. В Safari реализовали переключатель <input type="checkbox" switch>, а в Chrome вот-вот добьют главного босса — <select>. Стоит отдать должное инициативе OpenUI.

Команда Джен работает над спецификацией CSS Form Control Styling. Идея в том, чтобы значительно расширить возможности стилизации элементов форм и предоставить доступ к их внутренним частям.

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

В режиме базовой отрисовки появится доступ к внутренним частям элементов. Сейчас к некоторым из них можно обратиться через специальные псевдо-элементы с префиксами — ::-webkit-slider-thumb или ::-moz-range-thumb. Но они не стандартизированы, работают только в конкретных браузерах и отличаются набором поддерживаемых свойств.

Спецификация стандартизирует псевдо-элементы для доступа ко внутренним частям элементов форм. На данный момент их 15:

- ::picker() — выпадающий пикер у <select> и других элементов
- ::picker-icon — индикатор пикера
- ::file-selector-button — кнопка выбора файла у <input type="file">
- ::checkmark — индикатор выбранного состояния у <input type="checkbox">, <input type="radio"> и <option>
- ::thumb — ползунок у <input type="range">
- ::track — направляющая у <input type="range">, <input type="checkbox" switch>, <meter> и <progress>
- ::fill — заполненная часть у <input type="range">, <input type="checkbox" switch>, <meter> и <progress>
- ::field-text — редактируемая часть полей ввода
- ::clear-icon — кнопка очистки у <input type="search">
- ::step-control — блок с кнопками у <input type="number">
- ::step-up — кнопка увеличения значения у <input type="number">
- ::step-down — кнопка уменьшения значения у <input type="number">
- ::field-component — блок с компонентом даты/времени у соответствующих полей
- ::field-separator — разделитель компонентов даты/времени у соответствующих полей
- ::color-swatch — блок с выбранным цветом у <input type="color">

В спецификации есть несколько псевдо-классов для стилизации состояний элемента <meter>.

- :low-value — значение ниже оптимального
- :high-value — значение выше оптимального
- :optimal-value — значение в оптимально диапазоне

Есть в предложении интересная функция — control-value(). Она позволяет получать текущее значение элемента формы и выводить его в свойство content псевдо-элементов ::before/::after и других. Можно вывести значение и привязать его к ползунку <input type="range">.

Черновик стандарта находится в стадии разработки и многое ещё не продумано. Есть много issue, которые затрагивают важные вопросы. Выглядит амбициозно и на самом деле давно пора уже это сделать. Теперь нужно дождаться, когда это примут в CSSWG и начнутся первые эксперименты в браузерах.

#css
🔥21👍82
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