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

Автор: @alexnozer
Download Telegram
Cписок описаний

На прошлой неделе мне попался твит, в котором автор задавался вопросом, какие HTML-элементы с точки зрения семантики лучше подойдут для разметки такого контента:

Name: Anton
Email: xx@yy.com
Age: 80


Среди вариантов <table> и <dl>. Можно использовать <table> и сделать горизонтальную таблицу с заголовочными ячейками в первом столбце:

<table>
<tr>
<th scope="row">Name:</th>
<td>Anton</td>
</tr>
<tr>
<th scope="row">Email:</th>
<td>xx@yy.com</td>
</tr>
<tr>
<th scope="row">Age:</th>
<td>80</td>
</tr>
</table>


Если в таблице первая ячейка каждой строки — <th>, то она действует как заголовок этой строки. Можно явно указать область действия заголовочной ячейки атрибутом scope="row", как в примере. На мой взгляд <table> избыточен для такого контента. Таблицы нужны для объединения однородного набора данных, когда есть группа характеристик, для которых нужны общие заголовки и структурная связь.

Для разметки пар ключ-значение хорошо подходит элемент <dl>. Я решил написать об этом, потому что редко вижу элемент <dl> в разметке и вокруг него есть некоторые мифы. Разметка контента из примера при помощи <dl> будет выглядеть так:

<dl>
<div>
<dt>Name:</dt>
<dd>Anton</dd>
</div>
<div>
<dt>Email:</dt>
<dd>xx@yy.com</dd>
</div>
<div>
<dt>Age:</dt>
<dd>80</dd>
</div>
</dl>


<dl> — это список описаний или ассоциативный список. Многие ошибочно считают <dl> списком определений, поэтому применимость ограничивается только терминами и их расшифровками. Такое встречается не часто: на сайтах вроде Википедии, словарях или справочниках. Дело в том, что в HTML4 элемент назывался definition list и действительно был задуман как список определений. В HTML5 семантика <dl> изменилась и с тех пор это denoscription list. Его можно использовать не только для терминов, а для любых пар ключ-значение.

В <dt> описывается термин или ключ, а в <dd> соответствующие данные. Ключей и данных может быть несколько. Одному <dt> может соответствовать несколько <dd> и наоборот. У <dl> особая контентная модель, которая допускает использование <div> на первом уровне вложенности, если внутри него находятся только <dt> и <dd>. При этом <div> можно не использовать и вкладывать <dt> и <dd> напрямую в <dl>. В обычных списках так нельзя.

Как и <ul>, <ol> и <menu>, элемент <dl> — это список. Скринридеры озвучат количество элементов в списке и предоставят дополнительные возможности взаимодействия. В текущей версии ARIA in HTML указано, что у <dl>, <dt> и <dd> нет встроенных ролей. При этом Chrome добавляет свою роль DenoscriptionList для <dl> и роли term и definition для <dt> и <dd> соответственно. В Firefox всё аналогично, кроме роли <dl>, там она указана как definitionlist. Подробнее о взаимодействии скринридеров с элементом можно почитать в статье Адриана Розелли.

На практике <dl> подойдёт для разметки как списка терминов и определений, так и любого набора ключей со значениями. Например, в интернет-магазинах есть блок c характеристиками товара, где сначала идёт название характеристики, а затем значение. Данные в аккаунте пользователя тоже подойдут. Есть реализация паттерна Disclosure FAQ через <dl>. Обычный FAQ без “раскрывашек” тоже можно разметить с помощью <dl>. В целом если что-то выглядит как название свойства и значение, и таких пар несколько, скорее всего, это <dl>.
👍15🔥2🌚1
You don’t know HTML: слеш в конце элементов

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

<link rel="stylesheet" href="styles.css" />

<img src="image.jpg" alt="" />

<br />


На данный момент в HTML есть 13 пустых элементов:

- <base>
- <link>
- <meta>
- <hr>
- <br>
- <wbr>
- <source>
- <img>
- <embed>
- <track>
- <area>
- <col>
- <input>

Слеш в конце пустых HTML-элементов не нужен! Он не несёт никакой полезной нагрузки и игнорируется браузерами. Сделано это ради обратной совместимости. Слеш у пустых элементов пришёл из мира XML, где он строго обязателен. Хотя HTML — это XML-подобный язык и наследует оттуда многие идеи, он не совместим с XML, потому что имеет ряд отличий и использует другой парсер. Одно из таких отличий — не нужен слеш в конце пустых элементов.

Существует строгая, XML-совместимая версия HTML — XHTML. Она поддерживается браузерами, но уже практически нигде не используется. Там слеш нужен, так как этого требуют правила XML, которым следует XHTML. Если вы не пишите на XHTML, а вы на нём не пишите, то не используйте слеши, сэкономите пару байт, а то и килобайт размера документа.

Слеш для пустых элементов требуется в популярном синтаксисе JSX, который используется в React и некоторых других фреймворках. Тут важно понимать, что JSX — это не HTML в JavaScript, а специальный синтаксис (синтаксический сахар), мимикрирующий под HTML. JSX превращается в вызовы функций во время транспиляции исходного кода. В этом синтаксисе действуют свои правила, которые ближе к XML.

Другим источником слешей является Prettier — популярный инструмент форматирования кода. Он автоматически расставляет слеши для пустых элементов при форматировании HTML и HTML-подобных языков. Разработчикам Prettier долго об этом говорили, но они отказывались что-то менять. В итоге такое поведение отключается отдельным плагином.

Ещё один забавный момент со слешами случился во фреймворке Svelte. Его автор, Рич Харрис, был удивлён, что в HTML нельзя закрывать элементы как в XML (<div></div><div />), а парсер Svelte такое обрабатывал. Это приводило к некоторым багам из-за того, как браузер обрабатывает ошибки синтаксиса HTML. В итоге было создано issue с вопросом о том, как дальше быть. В Svelte 5 поведение приведено к стандарту HTML.

#ydkhtml
14👍5👎2😁2
Web Almanac 2024: Markup

Обзор данных из раздела Markup свежего Web Almanac 2024. Подсвечу моменты, которые показались мне интересными.

Общий размер сжатого HTML в этом году вырос и достигает 178кб в 90м перцентиле. Медиана составляет 33кб против 30кб в 2023 году и 31кб в 2022. При этом 11% страниц не используют сжатие. На остальных страницах выросло использование Brotli — 37%, который догоняет и постепенно вытесняет Gzip. Новый алгоритм сжатия Zstandard пока что особо не используется.

На 13% страниц не указан язык в атрибуте lang элемента <html>. Самый популярный язык — английский. Но с этим есть интересный момент: часто у страниц указан язык, который не соответствует реальному языку контента. Многие разработчики копируют сниппеты кода, используют стартовые шаблоны, emmet (! + Tab) или генераторы проектов, где по умолчанию стоит lang="en".

На 86% страниц есть как минимум один HTML-комментарий. При этом 26% страниц используют условные комментарии, которые работают только в старых версиях IE. Это много, учитывая смерть IE11, не говоря уже о его более ранних версиях. По всей видимости, это старые сайты, которые больше не поддерживаются. Комментарии лучше удалять из HTML вне режима разработки.

«Диватоз» всё ещё существует, <div> — самый используемый HTML-элемент, 29% всех элементов страниц. Топ-4 популярных элементов остаётся неизменным как минимум с 2021 года, а топ-8 совпадает с прошлым годом. Поменялись местами 9 и 10 места — <path> стал использоваться чаще, чем <meta>, что связано с ростом использования встроенного SVG. В 90м перцентиле на страницах используется 44 разных типов элементов. Общее количество используемых на странице элементов снизилось. Но в 90м перцентиле их всё ещё 1716, что больше рекомендуемого минимума.

Растёт количество пользовательских элементов (веб-компонентов): 7.9% в этом году, 5.4% в 2023 году и 3.6% в 2022 году. Рост частично вызван внедрением пользовательских элементов популярными сервисами: Wix, Revolution Slider в WordPress и Shopify. На страницах с пользовательскими элементами в целом больше JavaScript, потому что он нужен для их инициализации.

Самым распространённый атрибут — class, 33% от всех атрибутов. На 90% всех страниц есть хотя-бы один data-атрибут, самый распространённый — data-id. 14% страниц использует мета-тег viewport со значением, которое блокирует зум, а на 5% страниц этот мета-тег вообще не указан. Самый популярный формат favicon — png, использование выросло с 35% в 2021 году до 42% в 2024. ico постепенно уходит, с 33% в 2021 году до 27% в 2024. 18% страниц не используют favicon вообще.
👍3🔥21
Вишлист HTML

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

focusgroup

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

Invokers

Часто на небольших проектах нужно немного JS вида “при нажатии кнопки открыть диалог/добавить класс/изменить атрибут”. Хотелось бы делать это декларативно и без JS. Как с отправкой формы через <button type="submit"> внутри <form> или как Popover API. Инициатива Invokers предлагает декларативную привязку базовых действий к кнопкам с возможностью расширения через JS.

Custom Attributes

Custom Elements позволяет расширять стандартные HTML-элементы через атрибут is и наследование. Это давно реализовано в Chromium и Firefox, но в Safari отказались от идеи по ряду причин. Альтернативное предложение — Custom Attributes. Идея в том, чтобы создавать пользовательские атрибуты и добавлять их к элементам, тем самым расширяя их. Вариантов применения много. Также это помогло бы инструментам, которые полагаются на атрибуты, например HTMX, Alpine или Vue.

Declarative Custom Elements

Справедливое замечание в адрес веб-компонентов в том, что для регистрации требуется JS, даже если нет никакого поведения. Firefox догнал другие браузеры и внедрил Declarative Shadow DOM, теперь можно добавлять Shadow Root без JS. Хотелось бы дальнейшего развития идей Declarative Custom Elements, которые активно обсуждают в сообществе веб-компонентов.

Стилизуемые элементы форм

В браузере есть стандартные элементы форм, но они плохо стилизуются. Ситуация улучшается: appearance: none для сброса внешнего вида чекбоксов и радио, а на горизонте кастомизируемый <select>. Предложенный вариант выглядит интересно и хотелось бы, чтобы подобное решение распространилось за пределы <select>, на другие стандартные элементы, например <input type="date">.

UI-примитивы

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

Карусель

В моей работе практически в каждом проекте встречается карусель. Когда-то я использовал Owl Carousel и Slick, затем перешёл на Swiper и его веб-компонент, сейчас по возможности использую Shoelace Carousel. Но каждый раз это сторонняя библиотека, которую нужно устанавливать и настраивать. Мне нравится идея перенести карусель в CSS, расширив уже существующую и хорошо поддерживаемую спецификацию Scroll Snap.

DOM Parts и шаблоны

Одна из задач, которую решают фреймворки — декларативные шаблоны и обновление DOM при изменении данных. Веб-компоненты часто критикуют за отсутствие таких возможностей. Предложение DOM Parts предлагает решение для выделения обновляемых частей DOM. В сочетании с Signals и предложением Template Instantiation получится единый механизм шаблонов. Хотелось бы увидеть реализацию этих идей.

HTML Modules

В ранних спецификациях веб-компонентов был HTML Import, который в итоге был удалён из браузеров по соображениям безопасности. Ему на смену должны прийти HTML Modules, которые базируются на синтаксисе ES Modules и Import Attributes. Это позволит импортировать фрагменты HTML и создавать однофайловые веб-компонентов.

Больше внимания HTML

На самом деле хотелось бы, чтобы разработчики уделяли HTML больше внимания. Это фундаментальная технология веба, на которой всё строится. Используйте семантические элементы, валидируйте разметку, углубитесь в гайды ARIA, следите за чистотой и размером HTML.
4
Новогодние каникулы

Канал <divelopers> вместе с его автором уходит на новогодние каникулы и вернётся 9 января с новыми силами и постами. Спасибо что читаете!

Если хочется чего-то почитать на каникулах, предлагаю всю подборку постов You don’t know HTML (особенно если подписались недавно и пропустили). В ней я, вдохновившись серией книг Кайла Симпсона “You don’t know JS”, пишу про разные возможности HTML и Web API:

- You don't know HTML: download
- You don't know HTML: <datalist>
- You don't know HTML: input.showPicker()
- You don't know HTML: Media Capture
- You don't know HTML: inert
- You don't know HTML: <search>
- You don't know HTML: кнопки как ссылки
- You don't know HTML: Exclusive Accordion
- You don't know HTML: noscript и блок данных
- You don't know HTML: autocomplete
- You don’t know HTML: слеш в конце элементов

Если хочется послушать, предлагаю 443й выпуск подкаста “Веб-стандарты”, где я в качестве приглашённого гостя час обсуждаю с ведущими веб-компоненты. А если хочется посмотреть, есть запись доклада “What we do in the Shadow (DOM)” с MinskCSS, где я рассказываю как работает изоляция в Shadow DOM.

Всех с наступающими праздниками! Увидимся в Новом Году!
🔥9🎉84👍3🥰1
Возвращение к работе

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

В планах на этот год:

- увеличить частоту и стабильность выхода контента
- запустить несколько новых рубрик
- писать больше о доступности, производительности и веб-компонентах
- попробовать новые форматы постов

А ещё 5 января у канала был день рождения 🥳 Формально, канал создан 16 декабря 2023 года, но запуск произошёл 5 января 2024 года. За год собралось 434 подписчика и опубликовано 173 поста. Скромно, но у меня нет цели гнаться за цифрами. Канал создан для того, чтобы делиться полезным контентом по HTML, CSS, доступности, производительности, UX и веб-стандартам. Это те темы, которые мне интересны, в которых есть экспертиза и желание делиться.

Спасибо всем, что читаете! Буду рад, если поделитесь ссылкой на канал с коллегами и порепостите посты. Если у вас есть идеи, предложения или вопросы, смело пишите мне в личку.
👍16🔥7🎉4🌚2
Встроенный CSS-in-JS

В экосистеме JS-фреймворков распространён подход CSS-in-JS, когда стили пишутся в JS-файлах. В современных браузерах есть встроенный механизм для работы со стилями в JS — Constructable Stylesheets. С его помощью можно создавать таблицы стилей и применять их глобально к документу или Shadow Root.

Таблица стилей создаётся через конструктор CSSStyleSheet. Методы replace и replaceSync формируют стилевые правила из строки. В массиве adoptedStyleSheets у document или Shadow Root хранятся текущие таблицы стилей, туда же можно добавить созданную через API:

const stylesheet = new CSSStyleSheet();

stylesheet.replaceSync(`
.element {
color: #4e4e4e;
text-decoration: underline;
}
`);

/* применение к документу */
document.adoptedStyleSheets.push(stylesheet);

const div = document.createElement('div');
div.attachShadow({ mode: 'open' });

/* применение к Shadow Root */
div.shadowRoot.adoptedStyleSheets.push(stylesheet);


Из объекта CSSStyleSheet можно получить информацию о стилях: список правил и медиа-запросов, родительскую таблицу стилей (при использовании @import) и т.д. Интерес представляет список правил, с которым можно работать:

const firstRule = stylesheet.cssRules[0];

console.log(firstRule.cssText);
// '.element { color: #4e4e4e; text-decoration: underline; }'

console.log(firstRule.selectorText);
// '.element'

console.log(firstRule.style.color);
// '#4e4e4e'

console.log(firstRule.style.textDecoration);
// 'underline'

console.log(firstRule.style.cssText);
// 'color: red; text-decoration: underline;'


А ещё в JS появились Import Attributes. Можно импортировать стили как ES-модуль, указав тип css через оператор with. При импорте таким образом создаётся экземпляр CSSStyleSheet. Поэтому можно писать стили во внешних файлах и импортировать их в JS:

/* styles.css */
.element {
color: #4e4e4e;
text-decoration: underline;
}


import syncStylesheet from '/styles.css' with { type: 'css' };

console.log(syncStylesheet.cssRules[0].cssText);
// '.element { color: #4e4e4e; text-decoration: underline; }'

/* или через динамический импорт */
const dynamicStylesheet = await import('/styles.css', { with: { type: 'css'} });



Написав небольшую обвязку вокруг этих API, можно получить что-то вроде CSS Modules или Styled Components.
12👍5🔥3
You don't know HTML: fetchpriority

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

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

Атрибут fetchpriority можно указывать у элементов, которые загружают ресурсы: <link>, <noscript>, <img> и <iframe>. В качестве значений принимаются high, low и auto. Атрибут предназначен для изменения стандартного приоритета загрузки ресурса. Значение high делает ресурс более приоритетным, а low — менее приоритетным. Значение auto возвращает поведение по умолчанию, предоставляя браузеру выбор приоритета.

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

- loading у <img> и <iframe>
- preload у <video>
- defer, async и type="module" у <noscript>
- <link rel="preload">

fetchpriority — ещё одна такая возможность. Какие есть варианты использования:

- Загрузить важную картинку, которая влияет на LCP, с высоким приоритетом
- Понизить приоритет картинок в карусели, которые отображаются только при переключении слайда
- Предварительно загружать некритические ресурсы с низким приоритетом
- Понизить приоритет классического блокирующего скрипта в конце <body>
- Повысить приоритет асинхронного скрипта

Приоритет также можно указывать в функции fetch():

const response = await fetch('/resource', { priority: 'low' });


Подробнее о Fetch Priority API можно прочитать в статье на web.dev.

#ydkhtml #html #performance
🔥36👍2🌚1
Доступность контента в социальных сетях

Доступность важно учитывать не только при разработке сайтов, но и при публикации контента в социальных сетях. Делюсь хорошим сайтом accessible-social.com, на котором собраны гайды по созданию доступного контента и использованию функций доступности в социальных сетях.

Также предлагаю запись вебинара про доступный контент от Глафиры Жур, где можно посмотреть, как использовать те или иные функции доступности социальных сетей.

#a11y
👍75🔥5🥰2🌚1
focusgroup

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


В ARIA существуют некоторые виджеты, для которых нужно реализовывать управление фокусом и навигацию с клавиатуры. При перемещении по интерактивным элементам при помощи Tab и Shift+Tab, весь виджет должен быть одним шагом в последовательности табуляции. Навигация по внутренним элементам виджета должна осуществляться через клавиши со стрелками и специальные клавиши Home, End, Page Up, Page Down и другие.

Есть две стратегии реализации:

- виртуальный фокус и атрибут aria-activedescendant
- техника “Roving Tabindex” и манипуляция атрибутом tabindex

Обе техники требуют нетривиальных реализаций на JS с прослушкой множества событий клавиатуры и фокуса. Подводных камней хватает.

Команда Edge в рамках группы OpenUI предложила добавить в браузеры атрибут focusgroup для управления фокусом и клавиатурой в виджетах.

<div
role="toolbar"
focusgroup
aria-label="Text Formatting"
aria-controls="…"
>
<button
type="button"
aria-pressed="false"
value="bold"
tabindex="-1"
>
Bold
</button>
<button
type="button"
aria-pressed="false"
value="italic"
tabindex="-1"
>
Italic
</button>
<!-- ... -->
</div>


При перемещении по странице через клавиатуру, фокус попадёт на первую кнопка в тулбаре, не смотря на tabindex="-1". Браузер находит внутри элемента с focusgroup все интерактивные элементы и те, которые отмечены атрибутом tabindex="-1".

Перемещение между кнопками в тулбаре осуществляется стрелками. “Вверх” и “вправо” — следующая кнопка, “вниз” и “влево” — предыдущая. Кнопка “Home” переносит фокус на первую кнопку, а “End” — на последнюю. Предусмотрен зацикленный переход между первой и последней кнопками при указании focusgroup="wrap".

При выходе фокуса из тулбара, браузер сохранит последнюю сфокусированную кнопку и вновь сфокусирует её при возврате фокуса в тулбар. Это отключается, если указать focusgroup="no-memory".

Значения inline и block настраивают направление. inline — навигация стрелками “влево”/“вправо”. block — навигация стрелками “вверх”/“вниз”. При этом учитывается направление письма.

<div
role="toolbar"
focusgroup="inline wrap no-memory"
aria-label="Text Formatting"
aria-controls="…"
>
<!-- ... -->
</div>


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

Значение grid включает автоматическую табличную навигацию. Этот режим работает со стандартными HTML-таблицами и табличной раскладкой (display: table-*) для автоматического вычисления порядка фокуса при навигации. Дополнительные значения flow, col-wrap, row-wrap, col-flow и row-flow уточняют, как нужно переносить фокус между столбцами и строками таблицы.

Значение manual-grid включает ручную табличную навигацию. В этом режиме для строк и столбцов нужно дополнительно указать focusgroup со значениями grid-row и grid-cell. Это позволяет добавлять навигацию в пользовательские таблицы на основе <div> + flexbox или grid.

Для всех значений атрибута focusgroup предлагаются аналоги в виде CSS-свойств:

.element {
focus-group-type: linear; /* grid | manual-grid |
grid-row | grid-cell */
focus-group-direction: both; /* inline | block */
focus-group-wrap: none; /* wrap | flow | col-wrap |
col-flow | col-none | row-wrap | row-flow | row-none */
focus-group-memory: auto; /* none */
}


Есть экспериментальный пользовательский полифилл с поддержкой базовых функций (без табличной навигации и CSS) и демо-страница, на которой его можно опробовать. Также есть оригинальный полифилл от команды Edge, но он разработан для более раннего варианта синтаксиса, который уже изменился.

Статус функции пока не понятен. По результатам State of HTML. в focusgroup есть заинтересованность со стороны разработчиков. Браузеры, вроде как, тоже относятся положительно. В Chrome были какие-то эксперименты. Работа над предложением продолжается, хотя и не очень активно.

#html #css #a11y
8🔥7🌚2
JSX из прошлого

React популяризировал синтаксис JSX (JavaScript XML). На первый взгляд выглядит как HTML, но пишется в JS как есть, без кавычек и ближе по правилам к XML.

export default function TodoList() {
return (
<>
<h1>Hedy Lamarr's Todos</h1>
<img
src="https://i.imgur.com/yXOvdOSs.jpg"
alt="Hedy Lamarr"
className="photo"
/>
<ul>
<li>Invent new traffic lights</li>
<li>Rehearse a movie scene</li>
<li>Improve the spectrum technology</li>
</ul>
</>
);
}


Но такой код нигде работать не будет. При попытке запустить код в браузере, на первом же символе < возникнет ошибка синтаксиса. JSX — это синтаксическое расширение JS. Чтобы код работал в браузере, его необходимо транспилировать. Для этого используются соответствующие инструменты: babel, esbuild, swc, tsc и другие. В процессе вся HTML-подобная разметка заменяется на вызовы функций. JSX разработан для удобства описания разметки и композиции UI в JS.

Но когда-то похожий код в JS вполне работал. Существует стандарт ECMA-357 (PDF), который описывает расширение синтаксиса JS под названием E4X (ECMAScript for XML). Суть была в том, чтобы добавить в JS поддержку синтаксиса XML и предоставить возможности для работы с ним.

В конце 90-х и 00-x XML был распространённым форматом структурированных данных. Логично, что разработчики хотели удобно работать с ним напрямую в JS. Поддержка E4X была реализована в разных продуктах. Среди них Flash, Thunderbird, Firefox, OpenOffice и другие. Но в Firefox 10 E4X был объявлен устаревшим, а в Firefox 21 поддержка была полностью удалена. Работает ли E4X где-то сегодня, сказать трудно, информации практически нет.

Набор возможностей E4X был достаточно большим: XML-разметка в JS, подстановка данных из переменных, динамические имена элементов и атрибутов, добавление и удаление элементов, итерация по списку элементов, чтение и запись атрибутов, фрагменты, фильтрация по имени элемента и значению атрибута, JS-выражения в фигурных скобках, вызовы функций в разметке, сериализация в XML-строку и так далее. Посмотреть на примеры кода можно в архивных статьях MDN.

Со временем формат JSON вытеснил XML, а DOM API получил развитие и новые возможности. Необходимость в E4X пропала. Если нужно работать с XML, то для этого есть DOMParser и DOM API. Интересно, что со временем JSX стал распространённым синтаксисом и даже высказывались предложения по его стандартизации. При этом JSX не связан с E4X и уходит корнями к PHP и синтаксическому расширению XHP.

#js #xml
🤔12🤓4👍32🔥1
user-select

В CSS есть непримечательное свойство user-select. Оно управляет тем, как выделяется текст. Это свойство используется крайне редко, потому что к механизму выделения текста по умолчанию все привыкли и обычно менять ничего не нужно.

Я иногда использую user-select: none; чтобы отключить выделение у кнопок. Бывает, что при долгом нажатии на тач-устройствах или при быстром многократном нажатии происходит выделение подписи кнопки. На телефонах при выделении всплывает тулбар с кнопками “копировать”, “перевести”, зумится экран или ещё что-то происходит. Обычно для кнопок этого не хочется.

Недавно узнал, что у свойства user-select есть значение all из статьи Стаса Мельникова. Меня это натолкнуло на некоторые мысли, где и как это можно использовать.

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

И тут на сцену выходит user-select: all;. Свойство включает автоматическое выделение всего, что есть в текущем узле DOM и всех его дочерних узлах. Иными словами будет выделен весь текст узла. Это приводит к тому, что одиночное нажатие или тап выделяет всё сразу, включая спецсимволы и текст вложенных узлов.

Где это может пригодиться:

- Блоки с кодом. Как инлайновые <code>, <samp>, <kbd>, <var>, так и потоковые <pre> часто содержат код с кавычками, скобками, точками и прочим. А в <pre> часто вкладываются <span>-ы для реализации подсветки синтаксиса. К тому же сниппеты кода часто копируются для воспроизведения.
- Номера телефона, электронная почта. Если по каким-то причинам для их разметки не подходит <a> со схемой (tel:, email:), то можно обернуть в <span> с user-select: all;, чтобы номер или почта выделялись целиком в один клик, включая спецсимволы.
- Идентификаторы. Это может быть артикул товара, ID пользователя в системе, штриход, промокод, номер плательщика и так далее. Такие идентификаторы иногда содержат спецсимволы. Да и просто быстро выделить весь идентификатор одним кликом удобно.

Думаю можно ещё придумать места, где это применимо. В целом, если пользователи что-то часто копируют, то это может быть хорошим местом для применения user-select: all;. Думаю, это свойство также могут использовать разработчики WYSIWYG и текстовых редакторов.

В каком-то смысле свойство может быть ленивой реализацией кнопки “скопировать”, которую часто делают в блогах про разработку рядом с блоками кода. Нажали на код, выделился весь код в блоке, нажали Ctrl+C (или тапнули “копировать” на тулбаре). Это не заменяет отдельную кнопку “скопировать”, но будет как прогрессивное улучшение и дополнение к ней.

Кстати, у Стаса есть канал CSS isn’t magic. Там узнаю интересные штуки. Рекомендую посмотреть.

#css
🔥17👍84🤬1
Улучшения <dialog>

В ноябре прошлого (2024) года команда Chrome поделилась улучшениями <details>, которые сделают его удобнее с точки зрения DX. Теперь представлены улучшения элемента <dialog>. За последние годы его хорошенько подтянули по функциональности и доступности. Тем не менее, всё ещё есть неудобные моменты. Их и хотят решить новыми функциями.

Декларативное открытие и закрытие

Popover API предлагает декларативное создание кнопки-триггера через атрибуты popovertarget и popovertargetaction. Диалог можно открыть только вызовом методов в JS. Предложение Invokers Command предлагает декларативный способ открытия и закрытия диалогов через атрибуты по аналогии с Popover API. Я делал обзор предложения (со старым синтаксисом invokertarget/invokeraction)

<button type="button" commandfor="dialog" command="show-modal">
Show modal
</button>

<dialog id="dialog">
<p>I am some dialog content...</p>
<button type="button" commandfor="dialog" command="close">
Close dialog
</button>
</dialog>


Атрибут closedby

Это новый атрибут, который добавлен в спецификацию HTML. Он предназначен для управления механизмом закрытия диалога. При значении closerequest диалог закроется кнопкой Esc, жестом «назад», специальными жестами в скринридере, методом close() или командой close в HTML. При значении any, помимо указанных способов, диалог закроется при нажатии за его пределами или по ::backdrop. При значении none закрыть диалог можно будет только программно.

События beforetoggle и toggle

Для диалогов, поповеров и <details> разработаны новые события. beforetoggle происходит перед показом/скрытием элемента, а toggle — после. Оба события предоставляют свойства oldState и newState со значениями "open" или "closed". Так можно определить, элемент показывается или скрывается. preventDefault() в обработчике beforetoggle отменяет показ/скрытие.

scrollbar-gutter: stable

Свойство не новое и уже достаточно давно доступно в браузерах (в Safari недавно тоже завезли). Свойство управляет пространством, которое резервируется для скроллбара. Работает только для скроллбаров, которые занимают место, на плавающие скроллбары не влияет. Свойство решает проблему смещения страницы, когда при открытии диалога прокрутка блокируется через overflow: hidden.

***

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

#html
👍16🔥2🌚21
You don't know HTML: translate

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

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

Избежать этого поможет глобальный атрибут translate, который можно указывать у любого элемента. Он входит в стандарт HTML и принимает два значения: yes и no. Переводчики учитывают этот атрибут и не переводят текст внутри элементов, у которых установлено translate="no".

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

При вёрстке полезно указать translate="no" для блоков с контентом, который не нужно переводить: ФИО, email (если разделён на несколько частей), специальные названия, фрагменты кода, никнеймы. Важно помимо этого указать правильное значение атрибута lang у <html> и фрагментов контента на других языках.

Анастасия Батарей поделилась статьёй с примерами использования атрибута в рамках адвент-календаря HTMHell. Также короткой заметкой с примером поделился Стефан Джудис.

#ydkhtml #html
14👍10🔥3😱3🥰1
Анимация ключевых слов

В прошлом году в Chromium браузерах появилась возможность создавать анимацию между значениями длины и специальными ключевыми словами (intrinsic sizing keywords). Значения длины — это px, rem, % и так далее. Ключевые слова — это auto, min-content, fit-content и другие. Они доступны в качестве значений некоторых свойств, например height.

Проблема в том, что браузеру нужно вычислить значение, соответствующее ключевому слову на основе контента. Поэтому не совсем понятно, как анимировать 0px в auto. А auto это сколько? Теперь есть механизм, который включает интерполяцию. Браузер будет вычислять необходимые промежуточные значения и делать переходы между ними.

Включается такое поведение свойством interpolate-size: allow-keywords. Его можно указать у конкретного элемента, где это нужно, или глобально для всей страницы, так как свойство наследуемое.

:root {
/* Включение интерполяции для всей страницы */
interpolate-size: allow-keywords;
}

.accordion {
/* Или только для конкретного селектора */
interpolate-size: allow-keywords;
}


Классический пример — аккордеон. Нужно, чтобы высота блока с произвольным контентом плавно изменялась от 0 до высоты контента и наоборот. Так как контент произвольный, высота заранее не известна и хотелось бы использовать ключевое слово auto или max-content. Раньше для этого использовался JS, теперь можно сделать на CSS.

.accordion__content {
height: 0px;
overflow: hidden;
transition: height .5s;
interpolate-size: allow-keywords;
}

.accordion__button[aria-expanded="true"] + .accordion__content {
height: max-content;
/* или auto вместо max-content */
}


Другой способ — использовать новую функцию calc-size(). Это как calc(), только умеет работать с ключевыми словами.

.accordion__content {
height: 0px;
overflow: hidden;
transition: height .5s;
}

.accordion__button[aria-expanded="true"] + .accordion__content {
height: calc-size(max-content, size);
/* или auto вместо max-content */
}


Особенность calc-size() в том, что ко второму аргументу можно применять математические операции. Можно сделать так, чтобы аккордеон открылся на высоту контента и ещё дополнительные 20px:

.accordion__button[aria-expanded="true"] + .accordion__content {
height: calc-size(max-content, size + 20px);
/* или auto вместо max-content */
}


В статье на Chrome for Developers более подробно описано как это всё работает с примерами кода и видео (для браузеров, которые ещё это не поддерживают).

#css
🔥20👍5🌚21
Техника фасадов

Использование фасадов — это техника оптимизации загрузки виджетов сторонних поставщиков. Речь о виджетах, которые вставляются на страницу через <iframe> или <noscript> с инициализацией в <div>: карты, плееры YouTube, чат-боты, рейтинги и так далее.

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

Частично эти проблемы решаются ленивой загрузкой (<iframe loading="lazy">) и отложенными скриптами (<noscript defer>). Ещё можно выставить низкий приоритет загрузки. Не смотря на это, ресурсы всё равно загрузятся и обработаются, а пользователь может даже не будет взаимодействовать с виджетом.

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

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

У Вадима Макеева есть видео с реализацией фасада для встраиваемого плеера YouTube. Ролику уже 6 лет, но он всё ещё актуален и хорошо демонстрирует суть (вместо padding-хака для пропорций сейчас стоит использовать свойство aspect-ratio).

Для плеера YouTube есть готовый веб-компонент с реализацией фасада. В его описании есть ссылки на компоненты для Vimeo и других виджетов. Для Nuxt есть отдельное решение для сторонних скриптов, которое в том числе использует фасады. Lighthouse будет выдавать рекомендацию по использованию фасадов, если обнаружит на странице сторонние виджеты.

#performance
🔥21👍92🌚2
Функция attr()

В CSS давно существует функция attr(). Настолько давно, что она поддерживается в IE8. Её задумка в том, чтобы получать данные из атрибутов и применять их к свойствам в CSS. Если посмотреть синтаксис и параметры attr() на MDN, то это достаточно мощная функция. Нюанс в том, что браузеры поддерживают ограниченный набор возможностей. Сейчас функция работает только со строковыми значениями и только для свойства content.

<style>
a::after {
content: ' (ссылка: ' attr(href) ')';
}
</style>

<p>Подробнее про функцию <a href="https://developer.mozilla.org/en-US/docs/Web/CSS/attr">attr()</a> на MDN.</p>

<!--
Будет отрисовано:

Подробнее про функцию attr() (ссылка: https://developer.mozilla.org/en-US/docs/Web/CSS/attr) на MDN.
-->


Это ограничивает полезность attr() несколькими узкими кейсами. Один из таких — печатные стили. На бумаге нельзя нажать на ссылку и перейти на страницу, но можно вывести адрес, чтобы руками перепечатать в поисковую строку.

В Chrome 133 завозят обновления функции attr(), расширяя её возможности до уровня, описанного в спецификации и на MDN. Теперь её можно использовать во всех CSS-свойствах, указывать тип данных, единицу измерения и резервное значение.

Тип данных указывается после имени атрибута через type(). Это нужно, чтобы CSS правильно обработал строковое значение из атрибута и конвертировал его в нужный тип.

<style>
div {
color: attr(data-color type(<color>));
}
</style>

<div data-color="red"></div>


Поддерживаемые типы:

- <angle>: угол (90deg или 0.25turn)
- <color>: цвет (redrgb(255 0 0), #ff0000oklch(62.8% 0.2577 29.24), и т.д.)
- <custom-ident>: пользовательский идентификатор (используется для именованных анимаций View Transition или именованных якорей в Anchor Positioning)
- <integer>: целое число (5, 100)
- <length>: число + единица измерения (10px, 5rem, 40vw)
- <length-percentage>: как <length>, но ешё может быть в процентах
- <number>: число с опциональной плавающей точкой (5, 1.5, .25)
- <percentage>: процент (25%)
- <resolution>: разрешение (96dpi)
- <time>: время (3s или 3000ms)
- <transform-function>: функция трансформации (rotate(180deg))

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

<style>
div {
width: attr(data-size px); /* 50px */
height: attr(data-size px); /* 50px */
}
</style>

<div data-size="50"></div>


Вторым параметром в attr() можно указать резервное значение. Оно будет использоваться, если атрибут не указан, указан с пустым значением или не удалось конвертировать тип.

<style>
div {
width: attr(data-size px, 50px);
height: attr(data-size px, 50px);
}
</style>

<div data-size="big"></div>


Эти нововведения позволят передавать значения из HTML напрямую в CSS. Это возможно и сейчас с Custom Properties, но с attr() выглядит удобнее.

<p id="p1" style="--font-size: 2rem;"></p>
<p id="p2" data-font-size="2"></p>

<style>
#p1 {
font-size: var(--font-size, 1rem);
}

#p2 {
font-size: attr(data-font-size rem, 1rem);
}
</style>

<noscript>
const newFontSize = 1.5;

p1.style.setProperty('--font-size', `${newFontSize}rem`);
p2.dataset.fontSize = newFontSize;
</noscript>


Я думаю сообществу ещё предстоит открыть кейсы, где это можно применить. Но я уже вижу в этом пользу для контента, который настраивается через CMS. Можно будет записывать настройки из CMS в атрибуты и подтягивать через attr() вместо style с пользовательскими свойствами.

Больше подробностей и примеров в статье Уны Кравец и в блоге Chrome for Developers. Другие браузеры положительно смотрят на это нововведение и, вполне возможно, что тоже займутся его внедрением.

#html #css
🔥19👍75🌚2
RSS

В 459 выпуске Веб-стандартов слушатель задал вопрос:

Насколько актуален RSS и стоит ли его внедрять в сайты и приложения?


Пару недель назад мне попалась заметка I've been advocating for RSS support, and you should too. Автор призывает использовать RSS и приводит ряд преимуществ.

RSS — это набор основанных на XML форматов данных для предоставления обновлений контента. Плагин для CMS, фреймворка или инструмента сборки автоматически генерирует файл с XML-разметкой в специальном формате — фид. Он содержит список с заголовком, датой публикации, кратким анонсом, ссылкой, может содержать полный текст статьи и другие мета-данные. Фид обычно расположен в корне сайта под названием feed.xml или в виде ссылки с оранжевой иконкой, похожей на иконку Wi-Fi.

Разные программы и сервисы способны потреблять фиды, достаточно указать ссылку. Есть специализированные приложения — RSS-ридеры. Они позволяют подписаться на фид и получать обновления. Я, например, использую Feedly. У него более 5 млн скачиваний на Play Market, что говорит о достаточно большой аудитории. Есть и другие популярные аналоги.

В чём преимущество RSS:

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

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

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

- API. Фид может выступать в роли публичного API. Если нужно получить список статей, товары в интернет-магазине, новости с новостного портала или объявления с онлайн-доски, можно использовать фид как источник данных. Запросить через fetch(), использовать DOMParser для разбора и методы DOM API для ивлечения нужных данных.

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

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

#xml
12👍6👌5🔥4👨‍💻2🆒1
Про Lighthouse

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

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

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

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

Lighthouse в DevTools предназначен для получения общей картины производительности во время разработки. Это позволяет на ранней стадии отлавливать распространённые проблемы и регрессии. Можно думать об этом как о линтере. Здесь локальный запуск в DevTools играет на руку: можно делать контрольные замеры в начале работы, вносить изменения, повторно замерять и сравнивать.

Важно отметить, что баллы Lighthouse не влияют на SEO. Если аудит показывает максимальные баллы по всем критериям, для поисковой системы это ничего не значит. Влияют на SEO полевые метрики Core Web Vitals, собранные с реальных пользователей, посещающих сайт через Chrome. Их можно увидеть в отчёте CrUX при достаточном количестве посещений. Но цифры из Lighthouse коррелируют с Core Web Vitals.

В конце пару мыслей:

- Не полагайтесь на Lighthouse, если хотите отслеживать производительность сайта. Это не совсем релевантно, а полученные баллы ни на что, в общем-то, не влияют.
- Используйте Lighthouse для получения общей картины производительности во время разработки или для промежуточных замеров при работе над производительностью.
- Запускайте аудит для каждого ключевого типа страниц сайта. Желательно запускать аудит на максимально тяжёлых вариантах этих страниц.
- Для получения более реалистичной картины используйте PageSpeed Insights или WebPageTest. PageSpeed под капотом использует тот же Lighthouse, но аудит запускается на усреднённых устройствах и учитывает сетевые издержки. При наличии достаточного количества данных, PageSpeed покажет результаты Core Web Vitals из CrUX. WebPageTest также даёт много полезных данных, особенно если научиться правильно их интерпретировать.
- Если вы хотите серьёзно заняться производительностью, то в первую очередь ориентируйтесь на полевые метрики RUM (Real User Monitoring), Core Web Vitals и данные из CrUX. Постепенно можно разработать и внедрить собственные метрики.

#performance
👍20🔥158🌚3
Пользовательские функции в CSS

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


Многие возможности препроцессоров уже доступны в CSS, но функций и миксинов всё ещё нет. В прошлом году активно обсуждались идеи их добавления в CSS. А на днях Брамус поделился новостью, что команда Chrome занимается прототипированием пользовательских функций в CSS. Также был опубликован черновик спецификации CSS Functions and Mixins Module. Пока в нём описаны только функции.

В CSS появится новая директива @function для объявления функций. Для названия используется синтаксис <dashed-ident>, аналогично Custom Properties с двумя тире. После названия в скобках указываются аргументы, тоже в синтаксисе <dashed-ident>. Можно задать тип аргумента и значение по умолчанию. После списка аргументов можно указать тип возвращаемого значения через returns. Ключевое слово result внутри функции возвращает значение.

@function --shadow(--shadow-color <color> : inherit) {
result: 2px 2px var(--shadow-color, black);
}

.foo {
--shadow-color: blue;
/* будет синяя тень, так как --shadow-color наследуется */
box-shadow: --shadow();
/* будет красная тень, так как передано значение */
box-shadow: --shadow(red);
}


Внутри функции можно объявлять локальные переменные в виде Custom Properties. Они доступны внутри самой функции и в других функциях, которые вызываются внутри. Это отличается от того, как работают области видимости и замыкания в JS.

@function --outer(--outer-arg) {
--outer-local: 2;
result: --inner();
}

@function --inner() returns <number> {
/* --inner видит --outer-arg и --outer-local, так как вызвана внутри --outer */
result: calc(var(--outer-arg) + var(--outer-local));
}

div {
z-index: --outer(1); /* 3 */
}


Так как в CSS нет условий (пока что), циклов и структур данных, применение пользовательских функций ограничится генерацией параметризованных значений и трансформацией значений встроенными функциями calc(), color(), min() и другими. Но даже это звучит мощно и откроет новые возможности.

Более подробно о функциях в CSS можно узнать из статьи Мириам Сюзанн, соавтора спецификации.

#css
😱14👍94🌚1