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

Автор: @alexnozer
Download Telegram
CSS Wrapped 2025

Как и в прошлом году, команда Chrome DevRel презентовала лэндинг CSS Wrapped 2025. На нём собрано 17 новых функций CSS и UI, которые внедрены в течении 2025 года. Функции разделены на три группы и снабжены примерами.

Кастомизируемые компоненты

Invoker Commands. Декларативный способ управления отображением <dialog> и popover без JS. Также можно создавать собственные команды и вызывать событие command.

Атрибут closedby. Управляет закрытием <dialog>: только через JS или по нажатию Esc, жесту «назад» и аналогичным действиям или всё перечисленное плюс нажатие вне элемента.

Эвфемерные поповеры. Значение popover="hint" создаёт новый тип поповеров. Их можно вкладывать внутрь других поповеров и их появление не будет закрывать ранее открытые поповеры.

Стилизуемый <select>. Для <select> можно включить базовый режим, который отключает отрисовку системного элемента и делает его полностью стилизуемым.

CSS-карусели. Встроенный в CSS способ создавать карусели с кнопками «вперёд»/«назад» и маркерами. Есть ряд проблем, но они решаются.

Якорные контейнерные запросы. Стилизация элементов на основе положения связанного якоря из Anchor Positioning API.

Interest Invokers. Развитие Invoker Commands с вызовом команд по наведению указателя, фокусу и долгому тапу на тач-экранах.

Интерактивность нового поколения

Запросы состояния прокрутки. Контейнерные запросы для проверки наличия прокрутки, привязки элемента к точкам привязки или sticky-элемента.

Функции подсчёта элементов в дереве. Функция sibling-count() возвращает количество дочерних элементов. sibling-index() возвращает порядковый номер элемента внутри родителя.

scrollIntoView() для ближайшего контейнера. Дополнительный параметр container: 'nearest' для метода scrollIntoView() избавляет от некоторых багов с прокруткой.

Вложенные группы View Transition. Возможность вкладывать псевдо-элементы ::view-transition-group друг в друга для эмуляции 3D-переходов.

Перемещение элементов с сохранением состояния. Метод moveBefore() перемещает элемент по DOM-дереву с сохранением состояния. Полезно для <video> и <iframe>.

Улучшенная эргономика

Расширение attr(). Функция attr() расширена для работы с типами данных, чтения любых атрибутов и установки резервного значения.

ToggleEvent.source. Событию ToggleEvent добавлено свойство source с элементом, который стал источником вызова события.

Обрезка текста. Свойства text-box-trim, text-box-edge и сокращённое text-box позволяют обрезать лишнее пространство текстовых узлов для лучшего выравнивания.

Функция shape(). Описывает в похожем на SVG синтаксисе сложные фигуры, которые можно использовать как маски.

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

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

Расширенный синтаксис диапазонов. if() и style() только появились, а им уже добавили возможность проверки значений с помощью операторов <><= и >=.

Ключевое слово stretch. Новое значение свойств width и height. Оно позволяет элементу растягиваться на размеры содержащего блога, чего сейчас не добиться значениями auto, 100% и 100v*.

Свойство corner-shape. Предназначено для создания углов сложной формы, включая внутренние углы и вырезы в духе negative border radius.

Более подробно о каждой функции смотрите на лендинге: там есть демонстрации, примеры кода, видео, браузерная поддержка, статус Baseline и ссылки на дополнительные материалы. Ещё один крутой год для CSS и UI.

#html #css #ui
6👍3👾1
Custom Media Queries

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

Вот есть медиа-запросы, сейчас ещё появились контейнерные запросы. Также в CSS давно есть пользовательские свойства. Интуитивно хочется вынести значения медиа-запросов в пользовательские свойства и использовать удобные алиасы вместо значений.

:root {
--media-desktop: (width >= 750px);
--media-mobile: (width < 750px);
--media-reduced-motion: (prefers-reduced-motion: reduce);
}

/* Код ниже не сработает */
@media var(--media-desktop) {
/* Стили для десктопов */
}

@media var(--media-mobile) {
/* Стили для мобильных */
}

@media var(--media-reduced-motion) {
/* Стили для режима уменьшенного движения */
}


К сожалению, это не работает. В препроцессорах значение медиа-запроса можно поместить в переменную или сделать отдельный миксин для удобного использования. В спецификации Media Queries Level 5 подумали об этом и предложили директиву @custom-media.

С помощью этой директивы можно реализовать логику как в примере выше. Директива объявляет переменную, которая будет доступна в качестве значения медиа-запроса. Как это принято, имя переменной пишется в синтаксисе dashed-ident, с двумя дефисами вначале.

@custom-media --desktop (width >= 750px);
@custom-media --mobile (width < 750px);
@custom-media --reduced-motion: (prefers-reduced-motion: reduce);

@media (--desktop) {
/* Стили для десктопов */
}

@media (--mobile) {
/* Стили для мобильных */
}

@media (--reduced-motion) {
/* Стили для режима уменьшенного движения */
}


В @custom-media можно объявлять одиночные значения, перечислять несколько значений через запятую, использовать операторы and, or и not. Также доступен синтаксис диапазонов и такие запросы, как prefers-*, noscript, hover и другие.

Проблема в том, что ни один браузер не поддерживает этот синтаксис. Использовать его можно только с постпроцессорами. Для PostCSS есть плагин, а Lightning CSS транспилирует из коробки, если включить параметр drafts.customMedia: true.

На днях вышел Firefox 146. В конце релиз-ноутов есть раздел Experimental web features, где я увидел следующее:

Custom media queries: layout.css.custom-media.enabled

The @custom-media CSS at-rule defines aliases for long or complex media queries. Instead of repeating the same hardcoded <media-query-list> in multiple @media at-rules, it can be defined once in a @custom-media at-rule and referenced throughout the stylesheet whenever needed. (Firefox bug 1744292).


Это хорошая новость, потому что я жду эту фичу и наконец-то она сдвинулась с места. Конечно, есть шанс, что её удалят после экспериментов. Но хочется верить, что внедрение наоборот подтолкнёт остальные браузеры и фича попадёт в Interop 2026.

Под релиз этой экспериментальной функции на MDN написали статью с подробным описанием синтаксиса @custom-media и примерами кода. Рекомендую ознакомиться для лучшего понимания всех возможностей.

#css
13🔥3👾1
API пользовательских атрибутов

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

<noscript>
class ToggleButton extends HTMLButtonElement {
connectedCallback() {
// Логика
}
}

customElements.define(
'toggle-button',
ToggleButton,
{
extends: 'button'
}
);
</noscript>

<button
type="button"
is="toggle-button"
data-target="content"
>
Подробнее
</button>
<div id="content" hidden>
<!-- Скрытый контент -->
</div>


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

Этот API называется «Customized built-in elements» и давно поддерживается во всех браузерах, кроме Safari. Браузерные инженеры из Apple принципиально отказались реализовывать этот API, сославшись на целый ряд причин.

Если углубиться в детали, то причины вполне адекватные. Из-за отказа Apple от внедрения, Customized built-in elements практически не используются. Есть несколько альтернативных идей, которые учитывают обратную связь от Apple.

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


Одна из таких идей, Custom Attributes, обсуждалась на недавнем TPAC 2025. Есть несколько разных предложений, но основная суть в том, чтобы добавить API для создания пользовательских атрибутов по аналогии с пользовательскими элементами.

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

<!--
Это гипотетический код нового API
Он не работает ни в одном из браузеров
-->
<noscript>
class ToggleElementAttr extends Attr {
// Встроенный тип данных
static dataType = AttributeType.IDREF;
// Опциональное значение по умолчанию
static defaultValue = null;

// Элемент, у которого установлен атрибут
ownerElement;
// Текущее значение атрибута
value;

// Атрибут подключен к элементу
connectedCallback() {
// Логика
}

// Атрибут удалён
disconnectedCallback() {}

// Атрибут изменился
changedCallback(oldValue, newValue) {}
}

HTMLButtonElement.attributeRegistry.define(
'toggle-element',
ToggleElementAttr
);
</noscript>

<button
type="button"
toggle-element="content"
>
Подробнее
</button>
<div id="content" hidden>
<!-- Скрытый контент -->
</div>


Это предложение также решает проблему HTML-first библиотек, таких как HTMX, Alpine.js, UIKit и других, которые используют нестандартные атрибуты для декларативного добавления логики и анализируют их с помощью регулярных выражений или XPath.

Custom Attributes API позволит убрать лишний код, который отвечает за поиск и разбор атрибутов и их значений. Работа с произвольными атрибутами должна стать удобнее и проще. Также пользовательские атрибуты будут считаться валидными.

<noscript>
// Где-то в коде htmx.js может быть так:

class HxPost extends Attr {
// ...
}

class HxSwap extends Attr {
// ...
}

const buttonAttrMap = {
'hx-post': HxPost,
'hx-swap': HxSwap,
// ...
}

for (const name in buttonAttrMap) {
HTMLButtonElement.attributeRegistry.define(
name,
buttonAttrMap[name]
);
}
</noscript>

<button hx-post="/clicked" hx-swap="outerHTML">
Нажми меня
</button>


#html #js #web_api
123👾2🔥1
Разделение шрифтов на подмножества

Подобно тому, как большие JS-файлы разбиваются на несколько мелких и загружаются по необходимости, файлы шрифтов так же могут быть разделены на более мелкие подмножества. Эта практика называется «font subsetting» или «разделение шрифта на подмножества»

Количество глифов в шрифте зависит от поддерживаемых языков. Некоторые шрифты содержат практически все символы Unicode, в то время как шрифты с поддержкой только английского языка содержат ограниченный набор. Чем больше глифов, тем больше размер шрифта.

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

Латинские символы практически не будут использоваться, не говоря уже про символы из группы CJK (Chinese, Japanese, Korean) и других языков. Проблема та же, что с неиспользуемым JS-кодом, которая решается разделением кода и tree shaking. Аналогично можно поступить со шрифтами.

Один большой шрифт с множеством символов можно разделить на несколько:

- Шрифт с базовой кириллицей — основные буквы кириллицы, цифры, основные печатаемые знаки препинания и символы, можно ещё добавить типографические кавычки, длинные тире, многоточие и так далее;
- Шрифт с расширенной кириллицей — специфические буквы и символы (диакритические знаки) для Украинского, Белорусского, Болгарского, Сербского и других языков;
- Шрифт базовой латиницы — основные буквы латинского алфавита;
- Шрифт расширенной латиницы — специфические буквы и символы (диакритические знаки) для Немецкого, Польского, Литовского, Турецкого и других языков;
- Шрифты других подмножеств при необходимости.

Латиница на русскоязычном сайте пригодится для терминов, названий и переводчиков. Я посещаю англоязычные сайты и иногда использую функцию перевода страницы. Точно так же делают иностранцы. Не очень красиво, если вместо дизайнерского шрифта загрузится Times New Roman.

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

1. находите шрифт на Google Fonts;
2. копируете сгенерированную ссылку из <link rel="stylesheet">;
3. открываете ссылку в новой вкладке, там будет набор директив @font-face;
4. копируете нужные подмножества (помечены комментариями)
5. скачиваете соответствующие файлы шрифтов из src;
6. добавляете @font-face в проект и подставляете ссылки к скачанным файлам.

Другой способ — использовать Font subsetting. Загружаете шрифт, указываете символы или Unicode-последовательности и получаете шрифт, состоящий только из указанных символов. Третий способ — воспользоваться CLI-инструментом glyphhanger, подробнее в статье Зака Лезермана.

Чтобы браузер понял, когда и какое подмножество шрифта использовать, ему нужно знать, какие символы есть в шрифте. Эта информация задаётся свойством unicode-range внутри директивы @font-face:

/* cyrillic */
@font-face {
/* ... */
unicode-range: U+0301, U+0400-045F, U+0490-0491, U+04B0-04B1, U+2116;
}


Cвойство unicode-range содержит список символов в формате Unicode. Можно указать конкретный символ (U+2116 — знак номера «№») или диапазон (U+0410-044F — от буквы «А» до буквы «я»). Список значений Unicode можно посмотреть в таблице.

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

#css #performance
🔥74👾1
Про шрифты

Дополню предыдущий пост про разделение шрифтов на подмножества записями двух докладов. Первый от Вадима Макеева с интригующим и загадочным названием «_ ___ ______?» Из этого доклада я узнал про «сабсеттинг» и свойство unicode-range.

В докладе также рассказывается о свойстве font-display и как работают его значения, артефактах при загрузке, известных как FOUC и FOIT, локальных шрифтах, предварительной загрузке, резервных шрифтах. Не смотря на 2019 год, доклад всё ещё актуальный.

Второй доклад «Давай поиграем со шрифтами» от Никиты Дубко прям свежий, с прошедшей в октябре конференции Frontendconf 2025. Я также выступал на этой конференции, поэтому на докладе присутствовал лично.

Никита рассказал про устройство и метрики шрифтов, отрисовку в браузере, новые CSS-единицы, привязанные к метрикам шрифта, стратегии загрузки, новые свойства text-box-* и другие интересные моменты, например, в шрифтах можно программировать.

#css
9👾2🔥1
Я 💛 Фронтенд 2026

14 февраля буду выступать на «Я 💛 Фронтенд 2026» с очередным докладом про веб-компоненты. На этот раз от теории что такое веб-компоненты и как работает Shadow DOM перейду к чему-то более практическому и расскажу, как веб-компоненты используются в реальности. От забавных экспериментов до полноценного продакшина в крупных проектах.

#html #css #js #web_api
👍93🔥2👾1
Forwarded from Yandex for Frontend
This media is not supported in your browser
VIEW IN TELEGRAM
🛎 Всем, кто любит интерфейсы!

Приглашаем вас на «Я 💛 Фронтенд 2026» — самое масштабное мероприятие Яндекса по фронтенд-разработке, которое пройдёт 14 февраля в Москве и онлайн.

💻 На конференции мы соберём разработчиков и дизайнеров, которые жить не могут без создания красивых и удобных интерфейсов.

📎 В программе вас ждут хардовые доклады:

🟡 «По-настоящему адаптивный интерфейс». Спикер: Роман Ахмадуллин, лид фронтенда в Дроме

🟡 «Что на самом деле важно в разработке?». Спикер: Владимир Гриненко, предприниматель, экс-CTO Яндекс ID

🟡 «Vibe + Frontend = Velocity. Не просто вайб и не просто код». Спикер: Екатерина Вахромеева, ведущий разработчик пользовательских интерфейсов в Бюро 1440

🟡 «50 оттенков веб-компонентов». Спикер: Алексей Назаренко, Team Lead в ECORN Agency

Кроме докладов, будут дискуссии, нетворкинг и другие классные активности (да, и с подарками тоже 🎁). А если вы тоже хотите выступить, то присылайте заявки до 27 декабря.

➡️ Зарегистрироваться на ивент

Количество мест в офлайне ограничено, поэтому все заявки проходят модерацию.

🔅 До встречи!

Подписывайтесь:
💬 @Yandex4Frontend
Please open Telegram to view this post
VIEW IN TELEGRAM
👍43👎1🤝1
Новый Год

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

Тем не менее, хочу поздравить с наступающим, а кого-то уже с наступившим Новым Годом! Желаю хорошо провести праздники и отдохнуть как следует. И здоровья, болеть накануне — отстой.

Что касается канала, то он продолжит работу в обычном режиме с 12 января. Пока нужно отдохнуть от фронтенда, а навыки CSS можно применить в оформлении новогоднего стола.

А ещё 5 января у канала будет день рождения — 2 года. За 2025й год канал перешагнул отметку в 1000 подписчиков. Спасибо всем, кто читает, репостит, оставляет реакции и звёзды!

Описание изображения: «дивёлка» — скриншот вложенных друг в друга элементов <div> с эмодзи звезды вверху повёрнуты на 90 градусов и образуют ёлку.
121🎄7
Размер страниц имеет значение

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

Наткнулся на статью конца 2012 года от одного из членов команды разработки YouTube — Криса Захариаса. Он задался целью создать облегчённую версию страницы видео на YouTube общим размером до 100кб.

Дело было в 2009-2011 годах, устройства тогда были слабее, сети медленнее, а в браузерах не было столько крутых функций, как сегодня. Размер страницы YouTube в то время разросся до 1.2мб и десятков запросов.

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

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

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

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

Какие выводы можно сделать? Улучшение производительности сайта может открыть новые рынки и расширить аудиторию. При анализе статистики важна сегментация по географическому признаку.

Также предлагаю серию из 5 статей Тейлора Ханта «Making the world’s fastest website, and other mistakes» о том, как он задался целью оптимизировать продукт, над которым работал, и наглядно показать результаты коллегам и руководству.

#performance
👍172👾1
«У меня всё работает», или Как оптимизировать веб-приложения для рынков, где интернет не LTE

На глаза попалось видео с Frontendconf 2024, которое отлично дополняет предыдущий пост. Виктор Щеглов рассказывает о своём опыте запуска приложений на рынках со слабыми устройствами и сетями, а также делится советами по оптимизации.

Если история про оптимизацию YouTube относится к 2009-2011 годам, то Виктор в докладе рассказывает о современном положении дел. По данным Всемирного Банка в некоторых странах Африки, Азии, а также в Индии наблюдается экономический рост.

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

Рекомендую доклад к просмотру. Также Виктор поделился ссылкой на репозиторий «Web Performance Handbook», в котором он вместе с другом собирает различные материалы по веб-производительности.

#performance
👍53👾1
Почему CSS-in-JS плох для производительности?

Хороший вопрос для собеседований фронтенд-разработчиков по мнению Алекса Рассела: «почему CSS-in-JS плох для производительности?» Я заметил, что в сообществе последнее время чаще стали обсуждать эту тему.

В своё время CSS-in-JS стал решением для фреймворков (прежде всего для React), где нужен был способ стилизации компонентов в JS с изоляцией стилей, динамикой на основе состояния, темизацией и различными оптимизациями.

Всё это обеспечивается благодаря описанию стилей в виде объектов или шаблонных строк в JS-коде. Стили становятся частью компонентов и связаны с ними. Вместе с преимуществами такого подхода выявились недостатки.

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

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

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

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

Джоно Олдерсон отмечает, что хэш-классы, которые генерируют CSS-in-JS движки, ломают кэш, делают селекторы для аналитики и E2E тестов нестабильными, усложняют читаемость разметки и затрудняют повторное использование стилей.

Главный контрибьютор популярного CSS-in-JS решения Styled Components объявил о прекращении разработки, назвав среди причин уход сообщества от CSS-in-JS подхода и прекращение использования его на собственных проектах.

Если на проекте используется CSS-in-JS, рассмотрите возможность перехода на zero runtime решения. Они предлагают аналогичные возможности и синтаксис, но генерируют статические стили на этапе сборки проекта, например:

- vanilla-extract
- Panda CSS
- Linaria
- Compiled

Предварительно генерируемые CSS-файлы, подключаемые к странице через <link rel="stylesheet"> работают лучше. Динамику обеспечат пользовательские свойства, а классы можно сгенерировать на этапе сборки.

#css #js #performance
👍202👎1👾1
Хак с каскадными слоями для именованных медиа-запросов

Не так давно я рассказывал о Custom Media Queries, которые добавляют в CSS возможность создавать именованные медиа-запросы. Вместо того, чтобы каждый раз указывать значение, можно объявить его один раз и присвоить понятное имя.

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

Всё строится на каскадных слоях. Директива @layer позволяет перечислить слои в порядке приоритета применения стилей. Первый слой в списке — менее приоритетный, последний слой в списке — более приоритетный.

Директиву @layer с порядком слоёв можно вкладывать внутрь @media. В зависимости от значения медиа-запроса можно менять порядок слоёв. Это позволяет добиться желаемого эффекта и получить «именованные медиа-запросы»:

@media (width >= 1280px) {
@layer mobile, tablet, desktop;
}

@media (640px <= width < 1280px) {
@layer desktop, mobile, tablet;
}

@media (width < 640px) {
@layer desktop, tablet, mobile;
}

.grid {
grid-template-columns: repeat(
var(--columns),
minmax(0, 1fr)
);

@layer desktop {
--columns: 4;
}

@layer tablet {
--columns: 2;
}

@layer mobile {
--columns: 1;
}
}


На экранах шире 1280px слой desktop самый приоритетный, поэтому применится свойство --columns: 3;. На экранах от 640px до 1280px самым приоритетным становится слой tablet, поэтому применится --columns: 2; На экранах ниже 640px применится --columns: 1;

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

.grid {
/*
Приоритетнее слоёв, поэтому
колонок всегда будет 2
*/
--columns: 2;
grid-template-columns: repeat(
var(--columns),
minmax(0, 1fr)
);

@layer desktop {
/* Стили слоёв игнорируются */
--columns: 4;
}

@layer tablet {
/* Стили слоёв игнорируются */
--columns: 2;
}

@layer mobile {
/* Стили слоёв игнорируются */
--columns: 1;
}
}


Конечно, это — хак, на боевых проектах использовать не стоит. Но хак интересный и креативный. Выглядит вполне нормально, а каскадные слои Baseline Widely Available с марта 2022 года и на момент написания поддерживаются в 95.21% браузеров.

Если ну очень не хватает именованных медиа-запросов, чтобы отказаться от препроцессоров, то можно попробовать затащить. Хотя лучше дождаться внедрения официального решения Custom Media Queries из спецификации.

#css
👍6🔥4🌚41
Релиз jQuery 4.0.0

На прошлой неделе, в субботу, библиотеке jQuery исполнилось 20 лет, в честь чего выпустили релиз jQuery 4.0.0. Что? jQuery? Он ещё жив? Его всё ещё кто-то использует в 2026? Жив и всё ещё используется (данные за 2024 год, в альманахе за 2025 год нет данных).

Я писал об отношении к jQuery когда версия 4.0.0 вышла в стадию беты. Также своими мыслями поделился Никита Дубко, сославшись на статью «Why I Still Use jQuery». Всё ещё есть проекты и случаи, где применение jQuery уместно.

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

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

#js
👍13🔥31🥴1
You don't know HTML: <select size> и <select multiple>

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

Тем не менее, об этих возможностях полезно знать, потому что грядёт обновление, которое ещё больше расширит возможности стилизации. Об этом расскажу в следующем посте, а пока поговорим об атрибутах size и multiple.

Стандартный <select> отображается в виде блока с выбранным значением и небольшой стрелкой. При нажатии отображается выпадающий список с опциями. Выбор опции закрывает список и меняет текущее значение в блоке.

Если установить атрибут size со значением >1, то вместо блока с выпадающим списком отобразится прокручиваемый контейнер с опциями — listbox. Атрибут определяет, сколько опций должно быть видно в виджете.

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

Атрибут multiple включает множественный выбор опций. Как и с атрибутом size >1, при добавлении multiple вместо блока с выпадающим списком будет отображаться listbox. По смыслу это аналог группы флажков.

Нажатие по опции выбирает её, но не снимает выбор с других. Повторное нажатие по выбранной опции снимает выбор. Меняется поведение клавиатуры: стрелки навигируются по опциям, а Space выбирает/снимает выбор.

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

По умолчанию <select multiple> отображается как listbox с 4-мя видимыми опциями. Атрибут size можно использовать в сочетании с multiple для переопределения стандартного количества видимых опций.

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

#ydkhtml
5👍1👨‍💻1
Customizable select listbox

В понедельник я рассказывал об атрибутах size и multiple элемента <select>, которые позволяют превратить его в listbox и добавить множественный выбор опций. Это была подводка к сегодняшнему посту о стилизации listbox.

В Chrome 135 добавили поддержку стилизации элемента <select> на основе предложения «Customizable Select Element» от OpenUI. Сейчас это работает только с обычным <select>, атрибуты size и multiple не поддерживаться.

Команда Chrome продолжает работу и поделилась намерением внедрить стилизованный listbox. Это добавляет поддержку атрибутов size и multiple, делая listbox и его опции полностью стилизуемыми.

Брехт Де Рют написал у себя в блоге про грядущее обновление <select> со множественным выбором. Статья описывает некоторые изменения поведения мыши и клавиатуры, а также приводит примеры стилизации.

Как и с обычным <select>, свойство appearance: base-select меняет режим отрисовки виджета. Вместо системного компонента, который зависит от ОС, рисуется виджет на основе HTML и CSS с минимальными стилями.

В таком режиме появляется доступ к внутренним частям компонента, опциям, галочке выбранной опции, а также возможность вкладывать внутрь <select> и <option> другие HTML-элементы, например <noscript> или <img> для иконок.

Меняется взаимодействие мышью и клавиатурой. Не нужно удерживать Ctrl при клике для выбора/снятия выбора с опции. Но теряется сочетание Ctrl + A и Ctrl + Arrow для выбора всех опций разом и последовательного выбора.

Что касается стилизации, то показан красивый интерфейс выбора жанров музыки, который выполнен в виде сетки. Это достигается за счёт возможности обернуть все опции в <div> и добавить ему свойства CSS Grid.

Следующий пример в статье показывает сочетание атрибутов multiple и size="1". В этом случае рисуется <select> с выпадающим списком и флажками у опций. В режиме base-select доступны все расширенные возможности стилизации.

Пример иллюстрирует выпадающий список выбора ревьюверов. У каждой опции есть красный или зелёный индикатор, аватар пользователя, имя и фамилия. А весь виджет выполнен в «стеклянном» стиле с полупрозрачностью и размытием.

Возможности стилизации listbox планируют добавить в Chrome 145, который должен выйти уже сегодня. Но остаются некоторые нерешённые вопросы с элементом <selectedcontent>, который отображает выбранную опцию внутри <select>.

Дискуссии продолжаются. Брехт в конце статьи оставил ссылку на issue с обратной связью и поделился своими идеями. Другие браузеры пока не внедрили стилизованный <select>, но работы по внедрению начались в Firefox.

#html #css #ui
👍51🤝1
HTML Components: Componentizing Web Applications

Попалась мне интересная заметка W3C, которая хорошо подойдёт для нерегулярной рубрики «веб-археология». Называется она «HTML Components Componentizing Web Applications» и датируется аж 1998 годом.

Как известно, концепция веб-компонентов прорабатывалась в конце нулевых и была публично представлена Алексом Расселом на конференции Fronteers в 2011 году. Однако сама идея «компонентизации» HTML появилась гораздо раньше.

Команда из Microsoft подметила, что HTML со скриптами сильно разрастается при разработке веб-приложений и становится сложно формализовать логику и повторно использовать HTML. Была предложено концепция HTML Components (HTC).

Идея была в том, чтобы предоставить синтаксис для описания компонента и его свойств, методов, событий и логики для дальнейшего применения на разных страницах. Вот как это примерно выглядело (помните, 1998 год):

<HTML xmlns:PUBLIC="urn:HTMLComponent">
<PUBLIC:METHOD NAME="start"/>
<PUBLIC:METHOD NAME="stop"/>
<PUBLIC:PROPERTY NAME="direction" GET="getDir" SET="setDir"/>
<PUBLIC:EVENT NAME="onFlyFinished"/>
<PUBLIC:ATTACH EVENT="onclick" HANDLER="onClick"/>

<SCRIPT LANGUAGE="ECMAScript">
var direction = 0;
var x = 0;
var y = 0;
var xScaler = 0;
var yScaler = 0;
var bFlying = false;
var timer;

function start() {
switch (direction) {
case 0:
x = -100;
xScaler = 5;
y = 0;
yScaler = 0;
break;
case 1:
x = 0;
xScaler = 0;
y = -100;
yScaler = 5;
break;
case 0:
x = 100;
xScaler = -5;
y = 0;
yScaler = 0;
break;
default:
x = 0;
xScaler = 0;
y = 100;
yScaler = -5;
break;
}
bFlying = true;
element.style.position = "relative";
tick();
}

function stop() {
if (bFlying) {
window.clearTimeout(timer);
element.style.left = "0";
element.style.top = "0";
bFlying = false;
var eventElem = document.getElementsByTagname(
"public:event"
)[0];
var oEvent = createEventObject();
eventElem.fire(oEvent);
}
}

function setDir(dir) {
if (dir == "left")
direction = 0;
else if (dir == "top")
direction = 1;
else if (dir == "right")
direction = 2;
else
direction = 3;
}

function getDir() {
switch (direction) {
case 0:
return "left";
break;
case 1:
return "top";
break;
case 0:
return "right";
break;
default:
return "bottom";
break;
}
}

function tick() {
element.style.left = x;
element.style.top = y;
x += xScaler;
y += yScaler;
if (x == 0 && y == 0)
stop();
else
timer = window.setTimeout("tick()", 100);
}

function onClick() {
alert("x is '" + x + "%', y is '" + y + "%'.");
}
</SCRIPT>


Этот код определяет компонент с методами start и stop, параметром direction, событием onFlyFinished и обработчиком события onclick, по которому вызывается метод onClick. Вся логика и методы описаны в <SCRIPT>.

Затем этот файл определения компонента должен был подключаться к конкретному элементу на странице через CSS-селектор и свойство behavior. После этого у элемента появлялись все указанные свойства и методы.

<HTML>
<HEAD>
<STYLE>
#flier {
behavior: url(fly.htc);
}
</STYLE>
</HEAD>
<BODY>
<H1 ID=flier>Flying noscripts!</H1>
<BUTTON onclick="document.getElementsByTagname('H1')[0].stop();">
Stop
</BUTTON>
<SCRIPT>
var flyingElem = document.getElementsByTagname( "H1" )[0];
flyingElem.onFlyFinished = "alert('finished flying!');"
flyingElem.direction = "left";
flyingElem.start();
</SCRIPT>
</BODY>
</HTML>


Вот такой прообраз веб-компонентов из конца 90-х. Сегодня компонент определяется в JS, подключается через <noscript>, поведение добавляется по имени элемента, а свойства, методы и события объединены в класс.

Если отбросить детали синтаксиса, то некое сходство с современным Custom Elements API проглядывается. Более того, нечто отдалённо похожее на старый синтаксис, но на новый лад сейчас проектируется в рамках Declarative Custom Elements.

#html #js
👍7😎1
You don't know HTML: inputmode

HTML предлагает встроенные типы текстовых полей ввода для некоторых распространённых типов данных:

- number
- email
- url
- tel
- password

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

Во встроенных текстовых полях виртуальная клавиатура адаптируется под данные. Но существующие типы полей не всегда уместны: number и tel не подходят для номера карты, идентификатора или номера посылки.

В таких случаях стоит использовать обычное текстовое поле type="text" в сочетании с атрибутом inputmode, который управляет виртуальной клавиатурой. Сверху можно добавить JS для валидации соответствующих данных. Если требуется поле ввода 8-значного кода, состоящего только из цифр, можно использовать такое сочетание:

<label for="order_id">
Код заказа
</label>
<input
type="text"
inputmode="numeric"
pattern="\d{8}"
id="order_id"
name="order_id"
size="8"
placeholder="12345678"
aria-describedby="order_id_hint"
>
<span id="order_id_hint">
8 цифр, например: 12345678
</span>

Атрибут inputmode принимает одно из следующих значений:

- none — виртуальную клавиатуру отображать не нужно;
- decimal — клавиатура с большими цифрами, дополнительными знаками и разделителями для дробных чисел;
- numeric — клавиатура с большими цифрами;
- text — значение по умолчанию. Отображается стандартная клавиатура;
- tel — клавиатура с большими цифрами и дополнительными символами * и #;
- search — стандартная клавиатура, адаптированная для поискового запроса, вместо кнопки ввода может отображаться, кнопка с лупой;
- email — стандартная клавиатура, адаптированная для адресов электронной почты, включает символ @;
- url — стандартная клавиатура, адаптированная для URL-адресов, включает символ /.

Наиболее интересны первые три значения. Остальные соответствуют встроенным типам полей и лучше использовать их вместо inputmode. Тем не менее они пригодятся при создании собственных полей на базе contenteditable.

<p id="custom-label">
Email
</p>
<div
class="custom-input"
contenteditable
role="textbox"
inputmode="email"
aria-labeledby="custom-label"
...
></div>

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

#ydkhtml
🔥117👾2
Один большой CSS-файл или несколько маленьких?

Ситуация: у главной страницы интернет-магазина довольно низкий синтетический показатель производительности в Pagespeed. Сама страница состоит из шапки, hero-баннера, сетки из 8 карточек, формы подписки и подвала.

Анализ показал, что причина в высоких значениях FCP и LCP. Довольно долго отображается белый экран и ничего не рисуется. Проблема в блокирующих ресурсах, отрисовку блокирует большой CSS-файл со всеми стилями сайта.

Сайт устроен так, что стили разных разделов и компонентов объединяется в один CSS-файл, который подключается в <head>. Он блокирует отрисовку, браузеру нужно скачать и разобрать файл, прежде чем что-то рисовать.

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

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

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

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

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

Кэш в мобильных браузерах может быть вытеснен из-за более жёстких квот на дисковую память (cache eviction), даже если он технически валиден и время жизни не истекло. В итоге новая холодная загрузка большого файла.

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

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

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

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

С разделением файлов появляются накладные сетевые расходы. Каждый файл — отдельный запрос. Современные протоколы HTTP/2 и HTTP/3 по большей части решают проблему. Но загружать слишком много тоже не стоит.

Небольшие файлы также кэшируются. Изменение стилей корзины затронет только файл со стилями корзины и кэш инвалидируется только для этого файла. Остальные стили извлекутся из кэша.

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

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

#performance #css
112🤨7
Кнопки и ссылки

Время идёт, а дискуссии о кнопках и ссылках продолжаются. Недавно в чате веб-стандартов как раз обсуждали эту тему. Вроде бы всё очевидно: есть кнопка <button>, есть ссылка <a>, это разные элементы. Но неоднозначность сохраняется.

Ссылка <a> достаточно навороченный и сложный элемент. Никита Дубко когда-то целый доклад об этом сделал, рекомендую посмотреть. Суть элемента <a> крутится вокруг URL-адресов, навигации и контекстных действий над этими URL-адресами.

Кнопка <button> — не менее простой элемент. Исходя из встроенных возможностей, кнопка про управление формами, переключение поповеров, декларативный вызов команд и запуск произвольного JS-обработчика при нажатии.

Различия улавливаются: <a> про работу с URL-адресами, а <button> про работу с формами, UI-элементами страницы и произвольным JS. На сайтах, где в основном текстовый контент, понятно, что нужно использовать.

Ситуация усложнилась с развитием веб-приложений. Дизайнеры стали рисовать ссылки на другие страницы в виде кнопок и кнопки вызова действий в виде ссылок. Грань между подчёркнутым текстом и прямоугольником с текстом по центру размылась.

Разработчики не особо задумываясь используют <button> с location.href, если в макете нарисована кнопка и <a> с заглушкой в href и обработчиком нажатия, если в макете нарисован текст с подчёркиванием.

<!--
Код иллюстративный,
не надо так делать!
-->

<!-- «Кнопка-ссылка» -->
<button
class="button"
onclick="goTo('/catalog')"
>
Каталог
</button>

<!-- «Ссылка-кнопка» -->
<a
class="link"
href="#"
onclick="closeModal()"
>
Закрыть
</a>


Битва с дизайном проиграна: кнопки и ссылки рисуются по настроению, чутью и общепринятым шаблонам, а не по реальному назначению. CTA на баннере главной страницы магазина в 99% случаев будет ссылкой, которая выглядит как кнопка.

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

Вариативность дизайна и назначения порождает компоненты <ButtonLink> и <LinkButton>. Иногда появляются «универсальные бойцы», вроде <Action>, которые работают условно, в зависимости от переданных параметров (пропсов).

Так, к примеру, работает веб-компонент <sl-button>, который принимает атрибуты, свойственные как для <a>, так и для <button> и под капотом решает, какой HTML-элемент использовать. А его атрибут variant определяет внешний вид.

Высказываются идеи реализовать нечто подобное на уровне стандарта. Добавить новый тип кнопок (новое значение атрибута type), которые будут работать как ссылки с соответствующей семантикой. Но маловероятно, что такое появится в браузерах.

<!-- Иллюстрация идеи -->

<!-- Стандартная кнопка -->
<button
type="button"
commandfor="cart-drawer"
command="show-modal"
>
Корзина
</button>

<!-- Ссылка на базе кнпоки -->
<button
type="link"
href="/catalog"
>
Каталог
</button>


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

#html #ui
7🔥61
Прямая трансляция Я 💛 Фронтенд 2026

Сегодня состоится конференция Я 💛 Фронтенд 2026, на которой я вступлю с докладом про веб-компоненты. Присоединяйтесь к трансляции, начало в 11:00 по Москве, мой доклад в 11:50.
👍11🥰32😍1
50 оттенков веб-компонентов.pptx
29.5 MB
50 оттенков веб-компонентов

Только что выступил с докладом «50 оттенков веб-компонентов». Как и обещал, делюсь презентацией с выступления.
14👍5🔥1😘1