В этом выпуске 12 фич, которые стали доступы повесеместно с хорошей поддержкой. От трёх фич я в полном воссторге.
Обзор на браузерные API, которые стали Widely available в марте 2026. Раз в месяц я буду вам напоминать, что вы уже можете использовать в проде.
Каждый месяц выходят новые CSS-свойства, HTML-атрибуты, JavaScript-методы и WebAPI, но применять в проде мы их конечно же не будем.
2.5 года назад также каждый месяц выходили новые фичи в браузере, а вот их уже пора начинать применять.
Как мы понимаем, что уже можно использовать в проде?
У каждой компании, да что уж там компании, у каждой команды в компании своя методика принятия решения о внедрении той или иной фичи в проекте.
Общий же сценарий выглядит так:
- Посмотрели в пользовательские метрики. Поняли какими браузерами и их версиями в основном пользуются пользователи проекта;
- Заглянули в caniuse и поняли, какие фичи уже поддерживаются большинством браузеров;
- Приняли решение о внедрении той или иной фичи в проект.
Какие-то команды позволяют себе указывать правило "последние три версии браузеров". У других специфика проекта, что проект работает исключительно на iPad с Safari. Сами понимаете, все мы разные и требования разные, и у каждого свой подход.
Baseline - позволяет немного упростить процесс принятия решения о внедрении той или иной фичи в проект. Если фича Widely available значит фича уже как минимум есть во всех основных браузерах как минимум стабильно используются последние 2.5 года.
Какие фичи в вебе стали Widely available в марте 2026?
Clear-Site-Datacontain-intrinsic-size@counter-styleDevice orientation events
hyphenate-characterhyphensimage-set()<link rel="modulepreload">Overflow media queries
Storage manager
Update frequency media query
subgrid
1. Clear-Site-Data
Я считаю, что эта функция настолько крутая, насколько она недооценена, но доказать это не могу =)
HTTP-заголовок Clear-Site-Data даёт серверу способ попросить браузер самостоятельно сбросить локальные данные: кэш, куки, хранилища страницы, а при необходимости другие типы. Это сигнал клиенту: очистить то, что браузер держит у себя для этого сайта.
Когда это можно применять: выход из аккаунта, смена пользователя на одном устройстве, сценарии, когда после сессии нельзя оставлять данные в IndexedDB / localStorage, и восстановление пользователей после «битого» кэша или застрявшего service worker — без ручной чистки через DevTools.
Синтаксис
Каждая директива это строка в двойных кавычках. Без кавычек значение считается невалидным. Несколько директив перечисляют через запятую.
Clear-Site-Data: "cache" Clear-Site-Data: "cache", "cookies" Clear-Site-Data: "*"
Основные директивы:
"cache"— сбросить данные HTTP-кэша. В разных браузерах это может затронуть и смежное (например, bfcache, предзагрузки, шейдеры WebGL и т.п.)."cookies"— удалить все куки для зарегистрированного домена, включая поддомены. Вместе с ними сбрасываются и учётные данные HTTP-аутентификации."storage"— очистить DOM storage: в том числеlocalStorage,sessionStorage, IndexedDB, регистрации service worker (аналогunregister), устаревшие Web SQL / FileSystem / данные плагинов."executionContexts"(экспериментально) — сигнал перезагрузить контексты просмотра для origin (в духеLocation.reload())."clientHints"(экспериментально) — сброс сохранённых Client Hints (Accept-CH); в браузерах, где это поддерживается, подсказки могут очищаться и при"cache","cookies"или"*", так что отдельная директива нужна, когда вы хотите сбросить только их."prefetchCache"/"prerenderCache"(экспериментально) — очистка speculation rules: prefetch и prerender. Для обоих видов спекуляций обычно указывают обе директивы, если нужно вычистить всё."*"— очистить все типы данных, которые заголовок покрывает сейчас и будет покрывать в будущих версиях спецификации.
Примеры
Полный набор для типичного «мы точно вышли из сессии»:
HTTP/1.1 200 OK Clear-Site-Data: "cache", "cookies", "storage", "executionContexts", "prefetchCache", "prerenderCache" Content-Type: text/html
Только куки по зарегистрированному домену и поддоменам:
Clear-Site-Data: "cookies"
Практика:
Обычный POST /logout снимает серверную сессию, но в браузере нередко остаются IndexedDB, закэшированные ответы API, старый service worker и ключи в localStorage от предыдущего аккаунта. Clear-Site-Data закрывает эти проблемы полностью: не нужно перечислять в JavaScript каждое хранилище и гадать, не забыли ли что-то ещё.
Если часть аудитории застряла на старом бандле или сломанном кэше, на отдельной recovery-странице можно отдавать, например, "cache" и "storage", чтобы быстро вернуть клиент в консистентное состояние, особенно если подозреваете конфликт версий у service worker.
2. contain-intrinsic-size
contain-intrinsic-size — это сокращение для пары contain-intrinsic-width и contain-intrinsic-height. Оно задаёт размер, который браузер подставляет в раскладку, когда на элементе действует size containment (contain: size, content-visibility: auto / hidden и т.п.): движок вправе считать блок «как будто» фиксированного размера без полного пересчёта детей — ради меньшего числа reflow и более быстрого первого кадра.
Без явного внутреннего размера контейнер с удержанием размеров может вести себя так, словно внутри пусто — полоса прокрутки и позиция скролла начинают «плавать», когда невидимый блок внезапно получает реальную высоту. contain-intrinsic-size как раз отвечает на вопрос: какую площадь зарезервировать, пока содержимое не посчитано или намеренно пропущено.
Синтаксис
/* нет подставляемого размера по осям */ contain-intrinsic-size: none; /* одно число — и ширина, и высота */ contain-intrinsic-size: 1000px; /* ширина | высота */ contain-intrinsic-size: 300px 200px; /* auto + запасной размер (часто для content-visibility: auto) */ contain-intrinsic-size: auto 320px; /* отдельно для ширины и высоты */ contain-intrinsic-size: auto 400px auto 250px; /* auto none — без запомнённого размера ведём себя как none, а не как 0px */ contain-intrinsic-size: auto none;
Значения:
none— в данном измерении нет подставляемого внутреннего размера.<length>— явная ширина и/или высота для расчётов layout при containment.auto <length>илиauto none— пока элемент пропускает отрисовку содержимого (например, вне экрана сcontent-visibility: auto), браузер может использовать запомненный размер после того, как блок уже хотя бы раз отрисовали «по-настоящему»; если памяти нет — берётся<length>или поведениеnone(дляauto none).Одно значение (ключевое слово, длина или пара
auto …) распространяется на обе оси. Две длины или две парыauto …— это ширина, затем высота.
Примеры
Длинная лента: placeholder по высоте + возможность «запомнить» фактическую высоту после первого показа.
.feed-item { content-visibility: auto; contain-intrinsic-size: auto 280px; }
Карточка с тяжёлой внутренней вёрсткой вне viewport:
.product-card { content-visibility: auto; contain-intrinsic-size: auto 360px; }
Практика
Без contain-intrinsic-size блок с content-visibility: auto вне экрана может дать нулевую или слишком малую оценку высоты; при появлении в viewport страница пересчитывается, ползунок и якоря ведут себя непредсказуемо. Разумный fallback (auto 280px и т.д.) стабилизирует скролл; после первой отрисовки движок может опираться на сохранённый размер, не рендеря тяжёлое содержимое снова только ради замера.
3. @counter-style
@counter-style расширяет набор встроенных стилей списков и позволяет описать свой стиль счётчика, которого нет среди предопределённых. Внутри правила задаются дескрипторы: по ним браузер превращает целочисленное значение счётчика в строковое представление маркера.
В CSS уже есть много готовых стилей счётчиков, но @counter-style даёт способ собрать свой.
Синтаксис
Имя стиля — чувствительный к регистру <custom-ident> без кавычек, не равный none и не совпадающий с CSS-wide keywords. Лучше не называть стиль так же, как значения свойств списков/счётчиков, если хотите избежать путаницы.
Зарезервированы имена встроенных стилей для list-style-type: decimal, disc, square, circle, disclosure-open, disclosure-closed — их нельзя использовать как имя своего @counter-style. При этом они допустимы в других местах, где ожидается имя стиля счётчика (например, в system: extends <counter-style-name>).
@counter-style <counter-style-name> { /* один или несколько дескрипторов */ }
Минимальный пример:
@counter-style thumbs { system: cyclic; symbols: "\1F44D"; suffix: " "; }
Дескрипторы
system— алгоритм преобразования целого значения счётчика в строку. Если заданоcyclic,numeric,alphabetic,symbolicилиfixed, обычно нужен дескрипторsymbols. Дляadditiveнуженadditive-symbols.symbols— символы для маркеров (строки, изображения, идентификаторы).additive-symbols— для аддитивных систем (как римские): пары «символ + неотрицательный вес» по убыванию веса.negative— что дописать до и после представления, если значение счётчика отрицательное.prefix/suffix— что добавить перед и после основного представления маркера (суффикс идёт в том числе после части, которую задаётnegative).range— диапазоны значений, где стиль применим; вне диапазона используется цепочкаfallback.pad— минимальная длина представления (например, ведущие нули:01,02…).speak-as— как озвучивать маркер синтезом речи (screen readers).fallback— имя стиля счётчика на случай, если текущий не может построить представление или значение внеrange. Если цепочка обрывается, в конце браузер падает наdecimal.
Примеры
Стиль с фиксированным набором символов и применение к списку:
@counter-style circled-alpha { system: fixed; symbols: Ⓐ Ⓑ Ⓒ Ⓓ Ⓔ Ⓕ Ⓖ Ⓗ Ⓘ Ⓙ Ⓚ Ⓛ Ⓜ Ⓝ Ⓞ Ⓟ Ⓠ Ⓡ Ⓢ Ⓣ Ⓤ Ⓥ Ⓦ Ⓧ Ⓨ Ⓩ; suffix: " "; } .items { list-style: circled-alpha; }
Длинный упорядоченный список с start за пределами «запаса» символов уйдёт в fallback, если вы его зададите — иначе движок разберётся по правилам выбранного system.
Собственный маркер для list-style:
@counter-style rocket { system: cyclic; symbols: "\1F680"; suffix: " "; } ul.features { list-style: rocket; }
Свой стиль в counter() для нумерации разделов:
@counter-style chapter { system: numeric; symbols: "0" "1" "2" "3" "4" "5" "6" "7" "8" "9"; suffix: ". "; } .article { counter-reset: section; } .article h2::before { counter-increment: section; content: counter(section, chapter); }
Практика
В документе W3C Ready-made Counter Styles собрано более сотни готовых фрагментов @counter-style под разные языки и культуры. Удобно подставлять и проверять через Counter styles converter.
Если много текстового контента, сложная типографика или нестандартная нумерация, @counter-style позволяет держать формат маркеров в CSS, а не в шаблонах и генераторах HTML. Это упрощает локализацию и снимает лишнюю логику с бэкенда — при условии, что вы заранее продумали fallback, range и озвучивание через speak-as, где это важно для доступности.
4. Device orientation events
Device orientation events — это семейство DOM-событий, через которое страница узнаёт физическую ориентацию телефона или планшета и его движение (ускорение и скорость вращения). На мобильных устройствах к этому обычно подключаются гироскоп, акселерометр и магнитометр; на десктопе возможности ограничены железом, зато для сценариев «наклони телефон — поверни карту» API как раз заточен под сенсорные устройства.
Что слушать на window
По смыслу это три потока данных:
deviceorientation— ориентация относительно земной системы координат, когда браузер получил свежие углы (DeviceOrientationEvent).deviceorientationabsolute— то же, но в варианте абсолютной ориентации (когда агент это поддерживает).devicemotion— ускорение и угловая скорость вращения с заданной частотой (DeviceMotionEvent), то есть «как резко дёрнули устройство».
У части браузеров доступ к датчикам не открывается сразу. В таких средах перед подпиской на события вызывают статические методы DeviceOrientationEvent.requestPermission() и DeviceMotionEvent.requestPermission() — и только после явного действия пользователя (например, нажатия кнопки), как описано в разделе про разрешения в документации.
Синтаксис: ориентация
window.addEventListener("deviceorientation", (event /* DeviceOrientationEvent */) => { console.log(event.alpha, event.beta, event.gamma, event.absolute); });
Основные поля DeviceOrientationEvent:
alpha— поворот вокруг осиz(как «компас» в горизонтальной плоскости, в градусах);beta— наклон вокруг осиx(от −180 до 180);gamma— наклон вокруг осиy(от −90 до 90);absolute— признак абсолютной привязки к системе координат (зависит от устройства и браузера).
Синтаксис: движение
window.addEventListener("devicemotion", (event /* DeviceMotionEvent */) => { console.log(event.acceleration, event.accelerationIncludingGravity, event.rotationRate, event.interval); });
acceleration— ускорение без учёта гравитации (если доступно);accelerationIncludingGravity— с гравитацией;rotationRate— скорость вращения вокруг осей в градусах в секунду (DeviceMotionEventRotationRate);interval— интервал между измерениями в миллисекундах.
Пример
«Наклонная» карточка по gamma / beta
const card = document.querySelector(".card"); window.addEventListener("deviceorientation", (event) => { const x = Math.max(-15, Math.min(15, event.gamma ?? 0)); const y = Math.max(-15, Math.min(15, event.beta ?? 0)); card.style.transform = `rotateY(${x}deg) rotateX(${y * -1}deg)`; });
5. hyphenate-character
Свойство hyphenate-character задаёт строку, которую пользовательский агент показывает в конце строки непосредственно перед переносом по правилам переноса (hyphenation break). На это правило одинаково опираются и автоматические переносы (hyphens: auto), и мягкие (­ / U+00AD): отображаемый символ в точке разрыва берётся из текущего значения свойства.
По умолчанию действует auto: браузер подбирает подходящую строку с учётом типографических традиций языка контента. Явно задавать auto имеет смысл в основном тогда, когда нужно переопределить унаследованное значение (свойство наследуется).
Синтаксис
hyphenate-character: auto; hyphenate-character: "="; hyphenate-character: "—";
Значения:
auto— типовой символ переноса выбирает сам браузер в духе правил для текущего языка; это начальное значение.<string>— произвольная строка, которую нужно показать перед разрывом вместо выбора по умолчанию.
Примеры
Узкая колонка и явный символ вместо дефиса по умолчанию:
<p lang="en">Superc­alifragilisticexpialidocious</p>
p { max-width: 80ch; hyphens: auto; hyphenate-character: "="; }
Для стиля издания иногда просят тире вместо дефиса:
.editorial { hyphens: auto; hyphenate-character: "—"; }
Практика
Когда макет или гайдлайн требуют конкретного знака у переноса (равно, длинное тире, другой локальный вариант), раньше оставалось либо смиряться с дефолтом движка, либо избегать переносов. Теперь вариант фиксируется в CSS и автоматически тянется по дереву за счёт наследования.
В сайдбарах, сетке карточек и «газетных» колонках замена знака переноса часто визуально связывает набор с фирменной типографикой: переносы перестают выглядеть «чужими» по отношению к остальной пунктуации интерфейса.
6. hyphens
Свойство hyphens задаёт, как переносить слова по строкам: полностью отключить перенос внутри слова, оставить только явно отмеченные точки или разрешить движку самому расставлять дефисы там, где это допустимо для языка текста. Правила переноса зависят от языка; спецификация не фиксирует алгоритм до символа, поэтому детали могут чуть отличаться между браузерами.
Без продуманного переноса в узких колонках легко словить рваный набор, вынос за границы контейнера или широкие «провалы» при text-align: justify. С hyphenate-character (см. раздел выше) можно ещё и подобрать символ у разрыва строки, если дефис по умолчанию не подходит макету.
Синтаксис
/* Ключевые значения */ hyphens: none; hyphens: manual; hyphens: auto; /* Глобальные ключевые слова */ hyphens: inherit; hyphens: initial; hyphens: revert; hyphens: revert-layer; hyphens: unset;
Значения:
none— внутри слова перенос не выполняется, даже если в тексте есть мягкий перенос (­, U+00AD) или другие подсказки; строка ломается только по пробельным символам (вплоть до переполнения, если контейнер его не обрезает).manual— начальное значение. Перенос возможен только там, где автор явно указал возможность: мягкий перенос U+00AD (­в HTML) или обычный дефис U+2010 (видимый дефис, который ещё и отмечает допустимый разрыв).auto— браузер сам подбирает места переноса по своим правилам для отмеченного языка; при этом явно заданные точки (­, U+2010) имеют приоритет над автоматикой, если они есть.
Важно для auto: чтобы гарантированно подтянулись нужные словари и правила, у контента должен быть корректный lang в HTML. Иначе автоматический перенос может не сработать или работать непредсказуемо.
Примеры
Ручные переносы при manual (или при auto, где явные ­ переопределяют алгоритм):
<p lang="ru" class="copy-manual"> Электри­фи­ка­ция в узкой колонке — переносы только по мягким точкам. </p>
.copy-manual { hyphens: manual; }
Автоматический перенос длинного слова без разметки внутри:
<p lang="en" class="copy-auto"> Supercalifragilisticexpialidocious </p>
.copy-auto { max-width: 7ch; hyphens: auto; }
Практика
hyphens: auto особенно уместен там, где ширина ограничена:
карточки товаров и подписи;
ячейки таблиц с длинными терминами;
мобильные статьи и документация;
«книжные» колонки и режимы чтения.
Нюансы в связке с другой типографикой
При
word-break: break-allглиф переноса не рисуется, даже если разрыв произошёл «в логической точке» переноса — это отдельный режим разбиения, не классическая типографская расстановка дефисов.Разрыв по элементу
<wbr>не добавляет дефис в конец строки — в отличие от­и от автоматического переноса приauto.
7. image-set()
Функция image-set() задаёт несколько вариантов одного и того же изображения (URL, градиент и т.п.) и передаёт выбор подходящего варианта агенту пользователя. В первую очередь это нужно для экранов с высокой плотностью пикселей: браузер может взять 2x-версию там, где это уместно, и не тянуть лишние байты там, где нет.
Плотность экрана и условия сети у пользователей различаются: спецификация явно допускает, что на медленном мобильном интернете при высоком DPR агент разумнее предпочтёт менее тяжёлое изображение, чем ждать самое детализированное. Фронтендер перечисляет варианты, а конкретный выбор оставляет браузеру.
Синтаксис
Общая идея — список кандидатов через запятую:
/* по плотности пикселей */ background-image: image-set( url("image-1x.jpg") 1x, url("image-2x.jpg") 2x ); /* строка вместо url() допустима */ background-image: image-set( "tile.png" 1x, "tile@2x.png" 2x ); /* по поддерживаемому формату */ background-image: image-set( url("photo.avif") type("image/avif"), url("photo.jpg") type("image/jpeg") );
Для каждого кандидата задаётся:
Сама картинка —
url(...), строка с путём или другой допустимый<image>(в том числе градиент), но не вложенныйimage-set()— вложение запрещено.Опционально дескриптор разрешения —
1x,2x, единицыdppx,dpi,dpcm. Если дескрипторы разрешения используются, у каждого кандидата значение должно быть уникальным — два варианта с одним и тем же1xв одном наборе корректны не будут.Опционально
type("mime/type")— подсказка о формате; браузер может выбрать первый поддерживаемый вариант.
Дескриптор type() и разрешение можно комбинировать в одном кандидате.
Примеры
Retina-фон:
.hero { background-repeat: no-repeat; background-size: cover; background-image: image-set( url("/images/hero-400.jpg") 1x, url("/images/hero-800.jpg") 2x ); }
Приоритет современного формата с фолбэком (без дублирования одного и того же дескриптора разрешения — только type()):
.promo { background-repeat: no-repeat; background-size: cover; background-image: image-set( url("/images/promo.avif") type("image/avif"), url("/images/promo.webp") type("image/webp"), url("/images/promo.jpg") type("image/jpeg") ); }
Практика
Раньше адаптив под высокую плотность и «умные» форматы тяготел к <img srcset sizes> и картинкам в разметке, а фоновые и декоративные изображения часто оставались одним файлом «на всех». С image-set() тот же подход переносится в CSS: один раз описали набор — дальше выбор делает браузер.
Фон через image-set() — по-прежнему фон: скринридеры не озвучивают содержимое фона. Если от картинки зависит смысл страницы, её нужно дать alt в <img>, а не полагаться только на background-image.
8. <link rel="modulepreload">
`<link rel="modulepreload">` даёт декларативный способ заранее подтянуть модульный скрипт: скачать, распарсить, скомпилировать и положить результат в module map документа, чтобы позже выполнить его без лишней задержки. По смыслу это ближе всего к rel="preload", но для ES-модулей подготовка глубже, чем просто попасть в HTTP-кэш.
Раньше при <script type="module" src="main.js"> цепочка часто выглядела так: сначала грузится entry, уже из него браузер узнаёт статические import и последовательно дотягивает зависимости. С modulepreload вы можете параллельно начать загрузку entry и заранее известных зависимостей — и меньше ждать на этапе «обнаружил импорт → пошёл в сеть».
Синтаксис
Минимальный вариант:
<link rel="modulepreload" href="/app.js">
Обычно рядом остаётся сам модульный скрипт:
<link rel="modulepreload" href="/assets/app.js"> <script type="module" src="/assets/app.js"></script>
Отличие от rel="preload"
preload— в первую очередь скачает ресурс и удержит его в кэше; выполнение/компиляция модуля этим не гарантируется так же, как приmodulepreload.modulepreload— скачает, распарсит, скомпилирует и положит результат в module map, чтобы при фактическомimportили<script type="module">работа шла от уже подготовленного модуля.
У modulepreload режим запроса всегда cors. Атрибут crossorigin задаёт, уходят ли учётные данные (куки и т.п.): при anonymous или пустом значении по умолчанию credential mode ведёт себя как same-origin для отправки кук; при use-credentials — include, и учётные данные могут уйти и на кросс-ориджин (подробнее документации).
Атрибут as
Для rel="modulepreload" атрибут as необязателен и по умолчанию равен "script". Допустимы в том числе "style", "json" и «скриптоподобные» назначения вроде "audioworklet", "paintworklet", "serviceworker", "sharedworker", "worker". Если указать другое назначение, на элементе может сработать событие error.
Зависимости модулей
Браузер может (по своему усмотрению) дополнительно запросить зависимости указанного модуля — это оптимизация на стороне движка, а не жёсткая гарантия для всех браузеров. Чтобы везде попытаться заранее подтянуть цепочку, явно перечисляют каждый нужный URL отдельным <link rel="modulepreload" …>. События load и error на элементе относятся к тому ресурсу, который указали в href; автоматически подтянутые зависимости отдельных событий на главном потоке не порождают (мониторинг — через DevTools, service worker или логи на сервере).
Примеры
Только entry — уже убирает часть водопада до первого исполнения:
<head> <meta charset="utf-8" /> <title>Приложение</title> <link rel="modulepreload" href="/assets/main.js" /> <script type="module" src="/assets/main.js"></script> </head>
Entry и известные статические зависимости (main.js тянет modules/canvas.js и modules/square.js) — все три запроса стартуют раньше, чем парсер «дойдёт» до импортов внутри main.js:
<head> <meta charset="utf-8" /> <title>Модули</title> <link rel="modulepreload" href="main.js" /> <link rel="modulepreload" href="modules/canvas.js" /> <link rel="modulepreload" href="modules/square.js" /> <script type="module" src="main.js"></script> </head>
Практика
Если у вас React, Vue, Svelte или просто крупный бандл как набор ES-модулей, modulepreload — способ выиграть время без дополнительного JS в шаблоне: перечислили критичный entry и «широкие» статические зависимости, которые и так попадут в первый граф загрузки. Имеет смысл держать в прелоаде короткий список: то, без чего первый экран или главный сценарий не оживут.
Слишком агрессивный modulepreload конкурирует с LCP, шрифтами и тяжёлыми API вроде предзагрузки изображений. Держите список на уровне измеренных узких мест (например, один entry и 2–3 самых толстых чанка до гидратации), а остальное оставьте обычному графу import и кэшу — так вы реже «загоните» пользователя в очередь запросов без выигрыша по UX.
9. Overflow media queries
Медиавозможности overflow-blockи overflow-inline отвечают на вопрос, как устройство вывода обычно обращается с контентом, который не помещается в исходный содержащий блок — соответственно по блочной или по инлайновой оси. Это не проверка «есть ли сейчас полоса прокрутки у моего div»: признак описывает модель переполнения самого отображающего устройства, а не конкретного элемента в DOM.
overflow-block смотрит на блочную ось, overflow-inline — на инлайновую. При привычной горизонтальной письменности это чаще всего вертикаль и горизонталь.
overflow-block
none— контент, выходящий за пределы по блочной оси, не отображается.scroll— такой контент можно увидеть, прокрутив к нему.optional-paged— контент по-прежнему доступен через прокрутку, но пользователь или разметка могут инициировать разрыв страницы (в том числе через свойства вродеbreak-inside), после чего продолжение оказывается на следующей странице.paged— вывод постраничный: то, что не помещается на текущей странице по блочной оси, переносится на следующую.
overflow-inline
Здесь набор короче — только none и scroll с той же логикой, но относительно инлайновой оси (на экранах чаще горизонтальная прокрутка).
Синтаксис
@media (overflow-block: scroll) { /* Среда, где по блочной оси принято прокручивать */ } @media (overflow-inline: scroll) { /* Среда с прокруткой переполнения вдоль строки */ }
Подсветка параграфа, когда среда объявлена как scroll по нужной оси:
@media (overflow-block: scroll) { p { color: red; } }
Практика
Если среда ведёт себя как прокручиваемый экран, разумно опираться на scroll-контейнеры и ограничения по высоте окна:
@media (overflow-block: scroll) { .sheet { max-height: 100vh; overflow: auto; } }
Если же модель вывода постраничная (paged), важнее думать о разрывах страниц и о том, что блоки не рвутся посредине:
@media (overflow-block: paged) { .sheet { break-inside: avoid; } }
10. Storage manager
Интерфейс StorageManager — часть Storage API: через него браузер даёт странице оценить занятость и квоту хранилища для текущего origin, запросить «постоянное» хранение (чтобы данные реже вытеснялись при нехватке места) и проверить, выдано ли такое право уже сейчас. Дополнительно тот же объект открывает доступ к корню origin private file system— директории, недоступной произвольным путям в файловой системе пользователя, но удобной для крупных офлайн-данных.
Раньше о том, «сколько ещё можно писать в IndexedDB», приходилось гадать по косвенным признакам или ловить ошибки записи. Теперь estimate() возвращает числа в байтах; persist() и persisted() — явный слой над политикой вытеснения, без которого PWA и тяжёлые офлайн-сценарии часто вели себя непредсказуемо.
Методы
estimate()—Promiseс объектомusageиquotaдля origin (оценка примерная, браузер может округлять и не учитывать всё подряд).persist()—Promise<boolean>:true, если агент может сделать хранилище более устойчивым к очистке (решение остаётся за политикой браузера и, при необходимости, за пользователем).persisted()—Promise<boolean>: уже ли выдан режим персистентности.getDirectory()—Promise<FileSystemDirectoryHandle>на корень OPFS; имеет смысл, когда нужны файлы в приватной песочнице origin, а не толькоIndexedDBи Cache Storage.
Синтаксис
Минимальная оценка запаса места:
const estimate = await navigator.storage.estimate(); console.log(estimate.usage); // занято, байт console.log(estimate.quota); // ориентир квоты, байт
Примеры
Оценка объёма перед офлайн-кэшем
const { usage, quota } = await navigator.storage.estimate(); if (quota && usage / quota > 0.8) { console.warn("Почти закончилось место в локальном хранилище"); }
Сначала проверяют состояние, затем при необходимости запрашивают персистентность — так вы не дублируете лишние запросы и проще логируете поведение.
const isPersisted = await navigator.storage.persisted(); if (!isPersisted) { await navigator.storage.persist(); }
Умные стратегии кэширования: вместо модели «кэшируем всё, а там посмотрим» можно опираться на estimate() и политику persist:
не скачивать тяжёлый офлайн-пакет, если
usage/quotaуже высокий;заранее чистить второстепенные данные при приближении к лимиту;
вызывать
persist()там, где потеря локальных данных действительно критична (заметки, черновики, карты), а не «на всякий случай» на каждой витрине.
Для приложений вроде редактора или медиатеки getDirectory() + OPFS часто удобнее, чем гнать многомегабайтные блобы в одну только IndexedDB — но это уже отдельная история про File System API.
11. Update frequency media query
Медиавозможность update в @media позволяет проверить, как часто (и можно ли вообще) устройство вывода способно менять внешний вид контента после того, как он уже отрисован. Это не про FPS монитора в лоб, а про класс носителя: печать, e-ink, слабый экран или обычный дисплей.
На десктопе и в смартфоне чаще всего попадёте в fast, зато стоит включить печать, ридеры или экзотические терминалы — и запрос начинает отрабатывать осмысленно. Подробности и таблицу совместимости — в документации по update.
Синтаксис
@media (update: none) { ... } @media (update: slow) { ... } @media (update: fast) { ... }
Значения:
none— после первоначального вывода раскладку уже нельзя обновить: типичный пример — документ на бумаге (печать), где «экран» не меняется.slow— страница может перестраиваться по обычным правилам CSS, но устройство не успевает отображать изменения так, чтобы они выглядели плавной анимацией; в типовых примерах из документации — электронные книги и сильно ограниченные по скорости устройства.fast— динамические изменения по правилам CSS поддерживаются, носитель не аномально медленный, так что уместны регулярно обновляемые эффекты вроде CSS-анимаций.
Примеры
Анимация включается только там, где носитель считается «быстрым».
@keyframes jiggle { from { transform: translateY(0); } to { transform: translateY(25px); } } @media (update: fast) { p { animation: 1s jiggle linear alternate infinite; } }
Отключить движение там, где обновление есть, но медленное — чтобы не получить смазывание и лишнее моргание:
@media (update: slow) { * { animation-duration: 0s !important; transition-duration: 0s !important; } }
12. subgrid
Вложенный(sub) grid не задаёт собственный список треков, а берёт те же дорожки, что уже определены у родителя, в пределах области, которую занимает этот элемент.
Когда вы вешаете display: grid на контейнер, grid-элементами становятся только прямые дети. Их потомки идут в обычном потоке — если их не вывести в отдельный grid. Вкладывать сетки друг в друга давно можно, но вложенный grid по умолчанию независим: его колонки и строки не привязаны к родительской сетке. Отсюда классическая боль: карточки в каталоге, формы с подписями — всё это приходилось ломать об дополнительные обёртки, «магические» размеры или скрипты. subgrid даёт возможность внутренние элементы разместить на линиях той же сетки, что и снаружи.
Синтаксис
Вложенный элемент должен быть grid-контейнером; вместо repeat(...) или списка треков указываете subgrid по нужной оси (или по обеим):
.child { display: grid; grid-template-columns: subgrid; /* колонки — как у родителя, сколько «перекрывает» этот блок */ /* grid-template-rows: subgrid; — по желанию */ }
Число колонок/строк у subgrid не задаётся вручную: оно равно числу перекрытых родительских треков. Если элемент в родителе тянется с колонки 2 по 7, вложенная сетка по колонкам получит пять дорожек тех же размеров.
Нумерация линий и имена
Номера линий внутри subgrid начинаются с 1 заново — они не «наследуют» глобальные номера родителя. Зато это удобно для переиспользуемых компонентов: разметка внутри блока всегда с grid-column: 2 / 5, а сам блок вы ставите куда угодно на внешней сетке.
Имена линий родителя доступны для позиционирования внутри subgrid. Дополнительно можно объявить свои имена после ключевого слова, в квадратных скобках — они добавляются к родительским:
grid-template-columns: subgrid [a] [b] [c];
Зазоры (gap)
gap, column-gap, row-gap с родителя передаются во вложенную сетку с subgrid, чтобы промежутки совпадали. На subgrid-контейнере вы можете переопределить зазор — например, row-gap: 0, если нужно вернуть часть пространства из промежутка между строками родителя.
Примеры
Карточка в каталоге повторяет колонки внешней сетки:
.catalog { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); gap: 24px; } .card { grid-column: span 4; display: grid; grid-template-columns: subgrid; }
Блок полей формы тянется на всю ширину родителя и делит те же две колонки (подпись / поле):
.form { display: grid; grid-template-columns: 180px 1fr; gap: 12px 16px; } .field { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; }
Практика
С subgrid типовые сценарии — карточки с общей сеткой заголовков и метаданных, сложные формы, редакционные макеты — можно собирать без дублирования чисел колонок и без синхронизации ширин через JavaScript. По ощущениям это тот же вложенный grid: переключиться обратно на самостоятельные строки (убрав subgrid с grid-template-rows) несложно, если понадобилась неявная сетка по строкам. Учтите и обратную сторону: при minmax и контенте, который влияет на размер, треки могут расти — в том числе с учётом содержимого внутри subgrid и для родительской сетки.
На этом мартовские обновления подошли к концу. Поделитесь в комментариях о чём узнали новом и уже хочется попробовать на проекте.
В следующем месяце будет всего четыре новые фичи. До встречи в мае.
Скрытый текст
Привет. Я также пишу про CSS-спецификации простым языком. Веду фронтенд дайджест. Ежедневно исследую CSS. Создаю инструменты. Об этом всём я пишу(пока что) в телеграм канале, блоге и других ресурсах. Телеграм является входной точкой. Там без ереси, только код и живые встречи - https://t.me/greatAttractorCode
