ar
Feedback

لا تقع ضحية للمخادعين! تيليمتريو يكتشف ويُميّز هذه القنوات 👉 إذا كنت تريد رؤية العلامة، اشترك

Frontend VK Hub

Frontend VK Hub

الذهاب إلى القناة على Telegram

Комьюнити VK для фронтендеров. Кто и что стоит за интерфейсами, которыми пользуются миллионы пользователей, — от Почты Mail до VK Teams — рассказываем здесь 🤘

إظهار المزيد
1 120
المشتركون
لا توجد بيانات24 ساعات
لا توجد بيانات7 أيام
-130 أيام
أرشيف المشاركات

ES2026: что из нового стандарта можно брать в код уже сейчас ECMAScript обновляется раз в год, и к моменту утверждения станда
+5
ES2026: что из нового стандарта можно брать в код уже сейчас ECMAScript обновляется раз в год, и к моменту утверждения стандарта его новинки обычно уже работают в браузерах. 30 июня 2026 года Ecma International утвердила ES2026, семнадцатую редакцию языка. Интересного синтаксиса в ней нет, зато закрыты мелкие задачи, ради которых годами тянули зависимости или писали свои утилиты:  🔵Base64 из байтов 🔵точная сумма чисел 🔵Map со значением по умолчанию 🔵большие числа в JSON Почти всё это уже доступно в свежих версиях браузеров. #frontendvkhub #ecmascript

Frame-sizing в Chrome 154: iframe сам подстраивает высоту под контент Виджет комментариев, форма оплаты или дашборд во фрейме
Frame-sizing в Chrome 154: iframe сам подстраивает высоту под контент Виджет комментариев, форма оплаты или дашборд во фрейме почти всегда упираются в высоту. Хост не знает, сколько места нужно странице внутри, поэтому она меряет себя сама и шлёт размер через postMessage, а родитель ловит сообщение и меняет высоту фрейма. Этот мост каждая команда пишет заново, и пока первое сообщение не пришло, фрейм стоит с фиксированной высотой и прокруткой внутри. Chrome 154, вышедший 22 сентября, справляется без скриптов. Нужны две вещи: CSS-свойство frame-sizing на стороне хоста и метатег с согласием в документе внутри фрейма.
.embed {
  width: 100%;
  height: 500px; /* для браузеров без поддержки */
}

@supports (frame-sizing: content-height) {
  .embed {
    height: auto;
    frame-sizing: content-height;
    max-height: 80vh;
  }
}
Встраиваемая страница должна разрешить это явно, иначе хост её размер не получит:
<meta name="responsive-embedded-sizing"
      content="allow-origins=https://publisher.example">
Решение принимает встраиваемая сторона: в allow-origins через пробел перечисляются домены, которым можно сообщать размер, либо ставится *. Схема работает и для фреймов с другого домена и дополняет frame-ancestors из CSP. Тег должен быть в документе с самого начала, добавить его скриптом после загрузки не выйдет. Кроме content-height, у свойства есть content-width, логические content-inline-size и content-block-size, а также auto, которое оставляет прежнее поведение. Для виджетов, растущих вниз, подходит content-height. Размер браузер берёт после загрузки и дальше за каждым изменением вёрстки не следит. Если содержимое поменялось, например подгрузились новые комментарии, документ сам просит пересчитать размер:
if ("requestResize" in window) {
  window.requestResize();
}
Вызывать его лучше после того, как все изменения внесены, а не на каждом шаге, чтобы не уйти в цикл пересчётов. За удобство платят сдвигом вёрстки: фрейм вырастает уже после загрузки встроенной страницы и тянет вниз всё, что под ним, поэтому в первом экране такой виджет ухудшит CLS. Старый мост на postMessage пока выбрасывать рано, потому что в Firefox и Safari поддержки нет. Зато метатег во встраиваемые страницы можно добавлять уже сейчас, а на хосте включать frame-sizing под @supports и оставить прежний код запасным путём. А вы сейчас подгоняете высоту iframe через postMessage или живёте с фиксированной? #frontendvkhub #chrome #css

Vite+ — не новая версия Vite и не его замена. Это отдельная утилита, которая объединяет инструменты вокруг Vite в один CLI: V
Vite+ — не новая версия Vite и не его замена. Это отдельная утилита, которая объединяет инструменты вокруг Vite в один CLI: Vite 8, Vitest 5, Oxlint, Oxfmt, Rolldown и tsdown. 28 сентября VoidZero выпустила первый стабильный релиз. Разбираемся вместе с Петром Гилевичем, руководителем веб-разработки контентных сервисов ВКонтакте, как там всё устроено. В чём идея Фронтенд-проект требует набор инструментов: сборщик, тесты, линтер, форматтер, сборка npm-пакетов. Каждый обновляется сам по себе, у каждого своя конфигурация, а локальное окружение и CI расходятся — привет, worked on my machine. Заявленные кратные ускорения касаются линтинга и нового ядра сборки в Vite 8. Версии всех инструментов подобраны и протестированы вместе, обновляются одной командой и гарантированно работают в связке. Как устроено Глобальная утилита vp — отдельная программа, ставить через npm не нужно. Она смотрит, какая версия Node.js нужна проекту, скачивает её и переключает при переходе между проектами. В самом проекте вместо отдельных Vite, Vitest и Oxlint в зависимостях остаётся один пакет vite-plus — версии инструментов зашиты в него. Есть нюанс: если доставить какой-то инструмент отдельно, можно случайно получить вторую его копию и сломать связку — документация предупреждает об этом. Vite Task в монорепозитории и кеширование сборки Если вы разрабатываете несколько пакетов в одном репозитории, рутину — прогоны тестов и другие пайплайны — берёт на себя движок Vite Task. Он умеет то, ради чего раньше отдельно ставили Turborepo. Во-первых, он видит, какой пакет от какого зависит, и запускает сборки в правильном порядке: сперва библиотека, потом зависящее от неё приложение. Внутри одной сборки порядок модулей и так определяет сборщик — а вот очерёдность сборок между пакетами одного репозитория как раз зона ответственности Vite Task. Во-вторых, он кеширует сборки: vp run build --cache снимает fingerprint зависимостей модуля. Запустите команду снова, не меняя код, — сборка полностью пропустится, и результат мгновенно вернётся из кеша. Пока кеш хранится локально, но у Vite+ в планах удалённый — как у конкурентов, в том числе для использования на CI. Как Vite+ стал открытым Изначально Vite+ планировали сделать платным. Но до публичного релиза от платной модели отказались: код открыли под MIT. Почему передумали — в секции MIT License на сайте VoidZero прокомментировали: «Мы устали спорить, какие функции должны быть платными и как их ограничивать». Летом 2026 Cloudflare купила VoidZero — инструменты остаются открытыми. Что в итоге Получаем управление всей экосистемой вокруг Vite одной командой: один апгрейд вместо синхронизации пяти инструментов. Из коробки есть миграция: vp migrate пытается влить все конфигурации — линтера, форматтера, тестов — в один vite.config.ts, переписывает скрипты package.json на vp-команды и перед изменениями показывает план. Миграция рассчитана на свежие Vite 8+ и Vitest 4.1+; прехуки на Husky (утилита для Git-хуков) остаются как были — их подключают к Vite+ вручную. Отдельный CLI — знак того, что проект растят как целую экосистему для JS-проектов без привязки к пакетному менеджеру и предустановленному Node. Можно предположить, что дальше мы увидим поддержку других сред выполнения и возможность миграции между ними, но обещаний нет.  Старт мощный: 2,4 млн скачиваний в неделю на начало октября, мейнтейнер с мировым именем. Небольшим проектам, которые уже на Vite, пробовать можно. Крупным, вероятно, надо повременить: недавно Vite сменил ядро сборки на Rolldown, и это был очень серьёзный апдейт.  Непонятно, будут ли подобные обновления в ближайшие год-два. Экспериментальный Bundled Dev Mode может быть причиной новых мажорных изменений — тогда мигрировать придётся не один раз. #frontendvkhub #vite #что_нового

🔵STP 252: @supports научился проверять сами условия 11 сентября в Safari Technology Preview 252 добавили CSSConditionRule.su
🔵STP 252: @supports научился проверять сами условия 11 сентября в Safari Technology Preview 252 добавили CSSConditionRule.supports и named-feature() в @supports — проверку поддержки фич без детектируемого синтаксиса, например anchor positioning. Заодно распрефиксовали user-select — теперь это стандартное свойство без -webkit-. 🔵Safari 27 — новые возможности CSS и HTML 14 сентября вышел Safari 27 с кастомизацией <select>, псевдоклассом :heading, sizes="auto" и расширениями color-mix(). WebKit также обновил загрузчик ES-модулей, улучшив поддержку top-level await. 🔵Node.js 26.9 — быстрые Worker Threads и FFI 16 сентября Node.js ускорил запуск Worker Threads до 2 раз и снизил их потребление памяти. Экспериментальный node:ffi теперь включён по умолчанию — вызывать функции нативных библиотек можно без отдельного модуля C++. 🔵Chrome 154: браузер переходит на двухнедельный релиз-цикл 22 сентября вышел Chrome 154 — первый релиз по новому двухнедельному расписанию вместо прежнего четырёхнедельного. Из фич для вёрстки: • scroll-marker-group получил режимы links/tabs, которые задают фокус-порядок и ARIA-семантику для ::scroll-marker-group • iframe теперь можно сделать самоподстраивающимся под контент через frame-sizing: content-height — без ручного измерения высоты и postMessage 🔵Next.js: серия security-апдейтов закрывает критическую уязвимость в апстриме 22 сентября Vercel выпустил внеплановый патч 16.3.6 и 15.5.26 из-за критической уязвимости в сторонней зависимости. А 30 сентября — плановый сентябрьский security-релиз 16.3.8 и 15.5.27, закрывающий ещё 7 уязвимостей: 1 высокой степени критичности, 5 средних и 1 низкую. Если вы ещё не обновили проект на Next.js — не откладывайте это. 🔵Safari Technology Preview 253: sibling-index() добрался до @container 23 сентября в STP 253 sibling-index() и sibling-count() стало можно использовать прямо в условиях @container. То есть раскладка контейнера теперь может зависеть не только от его размера, но и от позиции элемента среди соседей. WebKit также обновил безопасное выравнивание (safe/unsafe) для абсолютно позиционированных боксов, чтобы они не вылетали за пределы исходного содержащего блока. #frontendvkhub #дайджест

CSS в 2026 году: где JavaScript уже не обязателен Открыть поповер, подсветить карточку с выбранным чекбоксом, растянуть texta
+5
CSS в 2026 году: где JavaScript уже не обязателен Открыть поповер, подсветить карточку с выбранным чекбоксом, растянуть textarea под текст — раньше на каждую такую мелочь заводился скрипт, состояние и подписка на события. За два года CSS забрал у JavaScript целый пласт таких задач, и часть фич уже прошла порог Baseline. Максим Кузнецов из команды веб-версий ВКонтакте разобрал, что из этого можно брать в продакшен прямо сейчас, что подключать осторожно, а за чем пока просто следить. ➡️В карточках — короткая версия с актуальными статусами поддержки. А подробнее читайте на Хабре. #frontendvkhub #css

URLPattern: маршруты, которые не нужно переписывать в регулярки В любом приложении со своей маршрутизацией рано или поздно за
URLPattern: маршруты, которые не нужно переписывать в регулярки В любом приложении со своей маршрутизацией рано или поздно заводится файл с регулярками. /^\/users\/(\d+)\/posts\/([\w-]+)$/ работает, но читается тяжело, номера групп приходится помнить наизусть, а проверка домена и протокола живёт отдельным условием рядом. Обычный выход состоит в том, чтобы притащить path-to-regexp и получить ещё одну зависимость ради разбора адреса. Разбирать адреса умеет сама платформа. URLPattern принимает шаблон в знакомом по роутерам синтаксисе и сопоставляет с ним URL целиком, а не только путь:
const route = new URLPattern({ pathname: "/users/:id/posts/:slug" });

route.test("https://app.example.com/users/42/posts/hello");  // true

const match = route.exec("https://app.example.com/users/42/posts/hello");
match.pathname.groups;   // { id: "42", slug: "hello" }
Группы именованные, так что возиться с их порядком не нужно. Кроме :name, доступны звёздочка для любого остатка, модификаторы ? и +, фигурные скобки для необязательных кусков пути и собственные регулярки прямо внутри шаблона: /v:version(\\d+)/ поймает только числовую версию. Шаблон покрывает все части адреса сразу, и на этом удобно строить таблицу маршрутов в Service Worker или в edge-функции:
const routes = [
  [new URLPattern({ pathname: "/api/v:v(\\d+)/*" }), handleApi],
  [new URLPattern({ hostname: "cdn.example.com" }), handleAssets],
];

self.addEventListener("fetch", (event) => {
  const route = routes.find(([pattern]) => pattern.test(event.request.url));
  if (route) event.respondWith(route[1](event.request));
});
Главная разница между двумя формами записи проявляется как раз тут. В объектной форме всё, что не указано, считается звёздочкой: шаблон выше совпадёт с любым протоколом и любой строкой запроса. В строковой форме, new URLPattern("https://example.com/books/:id"), пропущенные части приравниваются к пустой строке, поэтому адрес с параметрами мимо такого шаблона пройдёт, пока не дописать ?*#* вручную. Дальше идёт мелочёвка. Двоеточие внутри литерала экранируется обратным слешем, иначе парсер примет его за начало группы. Юникод в регулярных группах придётся кодировать процентами самостоятельно: автоматически платформа этого не делает. Регистр по умолчанию учитывается, а выключается опцией ignoreCase. С сентября 2025, когда поддержку закрыл Safari 26, API считается Baseline и работает в том числе в воркерах. Для старых браузеров есть официальный полифилл urlpattern-polyfill, он же закрывает Node без экспериментальных флагов. Смысл переезжать есть там, где роутер только сопоставляет адреса и достаёт из них параметры: слой регулярок или path-to-regexp в этом случае удаляется целиком. Если поверх маршрутизации навешаны вложенные layout-компоненты, middleware и ленивые импорты, URLPattern занимает нижний уровень, а остальное остаётся вашим кодом. #frontendvkhub #javascript #urlpattern

Как поделиться данными с воркером и не копировать их Воркер получает данные через postMessage, и браузер по умолчанию копируе
+5
Как поделиться данными с воркером и не копировать их Воркер получает данные через postMessage, и браузер по умолчанию копирует всё, что вы туда положили. На массиве в 100 МБ копирование само по себе подвешивает главный поток — ровно там, где воркер должен был его разгрузить. Обойти это можно двумя путями. Первый — отдать буфер воркеру насовсем: делается одной строкой и работает везде. Второй — открыть память, в которую оба потока пишут одновременно. Второй путь мощнее, но браузер за него просит дорого. Придётся отгородить сайт от чужих скриптов, видео и виджетов — и разбираться, что из этого после отвалится. Подробнее рассказываем в карточках. #frontendvkhub #воркер

Архитектурные векторы: альтернативы В предыдущем посте мы рассмотрели мейнстримную линейку: от серверных шаблонов до серверны
+6
Архитектурные векторы: альтернативы В предыдущем посте мы рассмотрели мейнстримную линейку: от серверных шаблонов до серверных компонентов. Параллельно с ней живут подходы, которые отвергают само допущение, что веб-приложение обязано быть JS/TS SPA с клиентским состоянием и JSON API. Разбираем в карточках альтернативные архитектурные подходы к фронтенду. #frontendvkhub #обзор #эволюция_веба

VK Frontend Meetup SPB — про веб-анимации, AI и автоматизацию 30 сентября в Санкт-Петербурге встречаемся с фронтенд-разработч
VK Frontend Meetup SPB — про веб-анимации, AI и автоматизацию 30 сентября в Санкт-Петербурге встречаемся с фронтенд-разработчиками на VK Frontend Meetup SPB. В этот раз разберём три слоя фронтенда: то, что видит пользователь, то, как мы создаём интерфейсы, и то, как масштабируем разработку. Поговорим о практических задачах, которые возникают в больших продуктах: как стабильно рендерить много анимаций, как научить AI верстать по правилам проекта, а не «примерно похоже», и как автоматизировать типовые задачи, чтобы запускать сотни промо без постоянного участия разработчиков. В программе: 🔵Артем Стукалов, МАХ — слой интерфейса и производительности. Расскажет, как команда пришла к собственной архитектуре для веб-анимации стикеров. Разберём, почему одного Lottie оказалось недостаточно, что изменилось после перехода на собственное решение и как удалось стабильно рендерить большое количество анимаций без лагов. 🔵 Эмиль Янгиров, Одноклассники — слой создания интерфейсов и AI. Поговорим о том, почему AI пока не умеет по-настоящему верстать production-код. Обсудим, как подготовить Figma и дизайн-систему к работе с AI-агентом, передать ему правила проекта, не дать выдумывать компоненты и автоматически проверять адаптивность, доступность и визуальное соответствие. 🔵 Александр Сарафанников, Почта Mail — слой масштабирования и автоматизации. Покажет, как запускать 300 промо в год за 10 минут. Это история о том, как команда прошла путь от единого места с коммуникациями и ручной работы разработчика до платформенного решения, которое позволяет запускать типовые промо сразу в нескольких проектах — без отдельной задачи для разработчика. Митап полностью офлайн — записей докладов не будет. Зато после основной программы останемся на фуршет и неформальное общение: можно будет задать вопросы спикерам, обсудить доклады и обменяться опытом с коллегами. Митап бесплатный, но для участия обязательна регистрация. Приходите — разберём фронтенд слой за слоем. #frontendvkhub #митап

Архитектурные векторы: мейнстрим Сегодня в продакшене одновременно работают разные архитектурные подходы: серверные шаблоны,
+8
Архитектурные векторы: мейнстрим Сегодня в продакшене одновременно работают разные архитектурные подходы: серверные шаблоны, jQuery, SPA, SSG, SSR, islands и Server Components. Эволюция веба — это не смена победителей, ведь каждая модель отвечала на свои вызовы. Разбираемся, как происходило развитие архитектур веба. #frontendvkhub #обзор #эволюция_веба

🔵18 августа вышел Firefox 154 — самый заметный для вёрстки релиз месяца. Появились text-box-trim, text-box-edge и шорткат te
🔵18 августа вышел Firefox 154 — самый заметный для вёрстки релиз месяца. Появились text-box-trim, text-box-edge и шорткат text-box. Проблема, которую они решают, знакома любому, кто верстал заголовки по макету. Шрифт несёт с собой пространство над и под строкой, причём у каждой гарнитуры своё: у одной сверху остаётся два пикселя, у другой шесть. Дизайнер в Figma видел текст, посаженный по оптическому краю, а в браузере тот же блок съезжал. Фиксилось это отрицательными марджинами, подобранными вручную, а при смене шрифта подбор начинался заново. Новые свойства срезают лишнее на уровне движка, и блок встаёт туда, где его нарисовали. Вторая пара — sibling-count() и sibling-index(). Первая возвращает число соседних элементов вместе с самим элементом, вторая — порядковый номер, счёт с единицы, а не с нуля. Раньше для стилизации по позиции шаблонизатор или скрипт проставлял каждому элементу инлайновый --index, от которого уже считались задержка анимации, толщина, прозрачность — список из полусотни карточек означал полсотни инлайновых стилей в разметке. Теперь это делает сам CSS, без nth-child-арифметики. 🔵Что зреет за флагами line-clamp заработал без вендорного префикса, правда пока без значения no-ellipsis, то есть обрезка с многоточием работает, а без многоточия ещё нет. Функция progress() считает значение по положению между минимумом и максимумом — заготовка для плавных зависимостей без акробатики с вложенными clamp(). В Nightly отдельно подъехала CSS Typed Object Model: работа со стилями из JS через типизированные объекты вместо разбора строк. До стабильной ветки ей далеко, но направление понятное. 🔵25 августа Chrome 152 ушёл в стейбл. ::backdrop, ::scroll-marker и ::view-transition теперь доступны как полноценные объекты CSSPseudoElement, а не только  как селекторы — закрыть диалог по клику на фон можно без ручного сравнения координат. Плюс появился relative alpha colors (alpha() из CSS Color 5) для точечного управления прозрачностью существующего цвета. Свойство window-drag размечает области установленного веб-приложения, за которые окно двигается как за заголовок. Кастомный тайтлбар в PWA перестал требовать возни с обработчиками мыши и вычислением дельты курсора. #frontendvkhub #дайджест

Customizable select: нативный дропдаун, который наконец стилизуется &nbsp;больше двадцати лет оставался чёрным ящиком. Шрифт
Customizable select: нативный дропдаун, который наконец стилизуется <select> больше двадцати лет оставался чёрным ящиком. Шрифт поменять можно, иногда — фон, дальше начинается территория операционной системы: своя стрелка, свои отступы, свой список опций, который рисуется вне окна браузера и никакому CSS не подчиняется. Дизайнер просит фирменный шеврон и скруглённые опции — разработчик идёт за Choices.js или Select2. Библиотека выкидывает нативный элемент и собирает вместо него стопку из div, после чего клавиатурную навигацию, ARIA-атрибуты и работу со скринридерами восстанавливают вручную. Тридцать килобайт и постоянная поддержка — цена за одну стрелку. Customizable select убирает этот обмен. Команда Safari показала его на WWDC 2026, работает он в Safari 27 и Chrome 135, Firefox прототипирует за флагом. Включается одним значением, но объявить его нужно дважды — на самом элементе и на выпадающем списке:
.custom-select {
  &,
  &::picker(select) {
    appearance: base-select;
  }
}
После этого браузер перестаёт рисовать виджет средствами системы и превращает <select> в обычное дерево элементов. Список опций уезжает в top layer, как поповер, и позиционируется через anchor(), а значит, больше не обрезается родителем с overflow: hidden. Открывается доступ к внутренностям, которых раньше не существовало. ::picker(select) — сам список, ::picker-icon — стрелка, ::checkmark — галочка у выбранной опции, :open — состояние раскрытого списка, :checked — текущая опция. Обычные селекторы, к которым применяются переходы и анимации. Отдельно стоит <selectedcontent> — он зеркалит содержимое выбранной опции внутрь кнопки. В той же опции теперь можно класть настоящую разметку: флаг картинкой, иконку, вложенный span с описанием. Раньше <option> принимал только текст, остальное браузер молча выбрасывал.
<select class="custom-select">
  <button>
    <selectedcontent></selectedcontent>
  </button>
  <option value="ru">
    <img src="/flags/ru.svg" alt=""> Россия
  </option>
</select>
Отличие от библиотечного подхода в том, что элемент остаётся нативным. Клавиатурная навигация, отправка в форме, семантика для скринридеров и валидация продолжают работать сами — меняется только внешний вид. При всём этом в базовом режиме список не рендерится за пределами окна браузера и не вызывает системные компоненты выбора на мобильных — вместо привычного колеса iOS пользователь увидит ваш список. Включение к тому же меняет разбор содержимого <select> парсером, и на старой разметке это может дать неожиданный результат. Chrome держит фичу под управляемым экспериментом на случай быстрого отката. До Baseline пока далеко, но браузер без поддержки игнорирует незнакомое значение и рисует обычный <select>. Фолбэков писать не нужно, а если хочется явно развести стили — есть проверка через @supports (appearance: base-select). #frontendvkhub #css #select

Speculation Rules: как готовить страницу до того, как по ней кликнули Между кликом по ссылке и появлением новой страницы всег
+5
Speculation Rules: как готовить страницу до того, как по ней кликнули Между кликом по ссылке и появлением новой страницы всегда есть пауза. Сервер может ответить за 30 миллисекунд, но дальше браузеру предстоит разобрать HTML, дотянуть стили и шрифты, выполнить скрипты — и человек всё это время смотрит на старую страницу. Speculation Rules переносят эту работу на время до клика. Пока пользователь читает текущую страницу, браузер готовит следующую в фоне, а после клика показывает уже готовую. В карточках — чем prefetch отличается от prerender, как настраивается момент срабатывания, какие лимиты держит Chrome и что делать с аналитикой, которая успевает выстрелить раньше визита. #frontendvkhub #html #speculation_rules

Подсветка текста, которая не трогает DOM Задача звучит просто: подсветить найденные слова на странице. Реализация традиционно
Подсветка текста, которая не трогает DOM Задача звучит просто: подсветить найденные слова на странице. Реализация традиционно упирается в то, что подсвечивать в вебе можно только элемент. Значит, нужно найти текстовый узел, разрезать его, обернуть кусок в <mark>, а при следующем запросе всё это разобрать обратно. Ломается такой подход быстро.  Range.surroundContents() бросает исключение, если диапазон пересекает границы тегов, а найденная фраза запросто начинается в <em> и заканчивается снаружи. Обработчики, повешенные на исходные узлы, теряются вместе с этими узлами. Виртуализованный список перерисовывается и уносит подсветку. Получается, что мы работаем с DOM ради того, что к структуре документа отношения не имеет.  Custom Highlight API решает это иначе — диапазон описывается, но в разметку не попадает:
const range = new Range();
range.setStart(textNode, 10);
range.setEnd(textNode, 25);

CSS.highlights.set('search', new Highlight(range));

::highlight(search) {
    background-color: oklch(90% 0.15 100);
    color: black;
}
DOM после этого выглядит ровно так же, как до. Диапазон живёт отдельно от дерева, а браузер рисует поверх текста при отрисовке. Пересечение границ тегов перестаёт быть проблемой: Range спокойно тянется через несколько элементов, потому что ничего оборачивать не требуется. Одна подсветка может содержать сколько угодно диапазонов. Зарегистрировать можно сколько угодно именованных подсветок — каждая со своим ::highlight(name) и своими стилями. Для совместной работы это то, что нужно: выделение каждого участника получает своё имя и свой цвет, а снимается всё через CSS.highlights.delete('name') или разом через clear(). Набор доступных свойств намеренно узкий: color, background-color, text-decoration, text-shadow и обводка. Геометрию поменять нельзя, background-image игнорируется. Ограничение осмысленное: раскладка от подсветки не зависит, поэтому браузеру не нужно пересчитывать её при каждом изменении диапазонов. По поддержке история долгая: Chrome и Edge закрыли её ещё в 105-й версии в августе 2022, Safari подключился в 17.2 в декабре 2023, Firefox замкнул круг позже, и с марта 2026 фича считается Baseline. На 2026 год она попала в фокус Interop. Механизм полностью зависит от JavaScript и молча ничего не делает там, где не поддерживается. Отсюда рабочая схема — оставить в разметке настоящий <mark> со стилями по умолчанию, а поверх включить ::highlight() через @supports selector(::highlight(search)). Читатель без поддержки видит семантическую подсветку, остальные — динамическую, и никто не остаётся с пустой страницей. А вы у себя чем подсвечиваете поиск по странице? Делитесь в комментах — интересно, кто уже выкинул обёртки в <mark>. #frontendvkhub #javascript

Как приоритет слоёв побеждает специфичность в CSS Каскад выбирает победителя по цепочке шагов: важность, origin, слой, специф
Как приоритет слоёв побеждает специфичность в CSS Каскад выбирает победителя по цепочке шагов: важность, origin, слой, специфичность, порядок объявления. На большой кодовой базе первые шаги обычно не используются, и всё сводится к гонке специфичности: reset с нулевой, дизайн-система со средней, продукт с высокой. Потом приходит правило вида .button.primary.active, и структура рассыпается. @layer добавляет шаг, который стоит выше специфичности. Слои объявляются один раз, от менее приоритетного к более:
@layer reset, base, components, utils;
Порядок фиксируется этим первым объявлением, дальше правила складываются в слои по имени, а место в очереди не зависит от того, где физически лежит код.
@layer reset {
  * { margin: 0; padding: 0; }
}
@layer components {
  .button { padding: 12px 20px; background: blue; }
}
@layer utils {
  .m-0 { margin: 0; }
}
.m-0 победит любой селектор из components, включая .card .button.primary:hover. Внутри одного слоя специфичность работает как обычно, между слоями она не значит ничего. Второй частый сценарий — сторонняя библиотека в отдельном слое. Statement @layer разрешён до @import, и ставить его первым надёжнее: порядок задан явно, а не собирается из очерёдности импортов.
@layer reset, vendor, base, components, utils;

@import url("normalize.css") layer(reset);
@import url("bootstrap.css") layer(vendor);
Bootstrap уезжает в vendor, наши компоненты лежат выше и перебивают его без !important. Всё, что написано вне слоёв, живёт в неявном верхнем слое и перебивает любой именованный, поэтому миграция идёт мягко: @layer подключается в новом коде, старый unlayered работает поверх и переезжает по частям. С !important порядок слоёв инвертируется, important из reset перебьёт important из utils. Менее очевидная часть той же механики: unlayered important оказывается слабее любого слоя. Правка, которая годами держала переопределение поверх всего, после переезда библиотеки в слой молча перестанет работать. Слои вкладываются друг в друга: @layer components { @layer forms, buttons, cards; }. Приоритет считается по иерархии, а весь блок components занимает одно место в глобальном порядке. Рабочая раскладка обычно укладывается в шесть слоёв: reset, tokens, base, components, utils, overrides. Поддержка приехала в браузеры ещё в 2022, сейчас @layer в статусе Baseline widely available. Единственное — Safari 15.3 и старше выбрасывает содержимое слоя целиком, без @supports там будет пустая страница. Порядок объявляется один раз и дальше не требует ни сборки, ни договорённостей внутри команды. А в ваших проектах слои уже разложены или порядок стилей всё ещё держится на очерёдности импортов? #frontendvkhub #css

Функции и условия в CSS без препроцессора Кастомные свойства решили половину задачи: значение можно вынести в переменную и пе
+5
Функции и условия в CSS без препроцессора Кастомные свойства решили половину задачи: значение можно вынести в переменную и переиспользовать. Но переменная хранит ровно то, что в неё положили, — посчитать что-то на её основе она не умеет. Отсюда Sass, который считает на этапе сборки и потому не видит того, что происходит в браузере. Теперь CSS умеет это сам. @function описывает функцию с аргументами и типами, if() возвращает разные значения по условию — и то и другое работает во время выполнения, с живыми значениями переменных. #frontendvkhub #css

CSS @scope — изоляция стилей без Shadow DOM и препроцессоров Изоляция стилей в CSS долго решалась косвенно: BEM с длинными им
CSS @scope — изоляция стилей без Shadow DOM и препроцессоров Изоляция стилей в CSS долго решалась косвенно: BEM с длинными именами, CSS Modules с хешированием, scoped-атрибут в Vue, Shadow DOM в Web Components. У каждого варианта своя цена. @scope даёт нативный способ ограничить область действия правил. В Chrome 118 с октября 2023, Safari 17.4 с марта 2024, Firefox 143 с сентября 2025 — и в 2026 везде. Идея простая. Внутри @scope правила применяются только к элементам в поддереве заданного корня:
@scope (.card) {
    h2 { color: purple; }
    p  { line-height: 1.6; }
}
Все h2 и p внутри .card получат эти стили. Классов вроде .card__title не нужно: селектор внутри scope пишется как есть, границу задаёт сам scope. Верхняя граница задаётся через to:
@scope (.article) to (.comments) {
    p { font-size: 1.1rem; }
}
Правило применяется ко всем p внутри .article, но не проникнет в .comments и вложенное. Решает классическую задачу: статья и блоки комментариев стилизуются независимо, но рендерятся внутри одной иерархии. Второй вариант — inline-scope через <style> внутри HTML. Тег <style> с @scope без селектора привязывается к родителю:
<div class="widget">
    <style>
        @scope {
            h3 { color: teal; }
            button { background: navy; }
        }
    </style>
    <h3>Заголовок</h3>
    <button>Кнопка</button>
</div>
Ближайший CSS-аналог <style scoped> из Vue: описываем стили прямо в компоненте, они применяются только к его поддереву. Без прекомпилятора и обёрток. Правила внутри @scope считаются с той же специфичностью, что и снаружи, но при равной выигрывает правило, находящееся ближе к элементу по DOM. Правило из внутреннего @scope перебьёт правило из внешнего. Практический паттерн — темизация секции:
@scope (.section--dark) {
    :scope { background: #1a1a1a; color: #eee; }
    a { color: #7cb; }
    button { background: #333; color: #eee; }
}
:scope — сам элемент, к которому привязана область (аналог :host в Shadow DOM). Всё внутри .section--dark перекрашивается разом, без каскадных префиксов и без глобальных override. @scope ограничивает только те правила, что внутри самого @scope. Не блокирует утечку правил снаружи: если во внешнем CSS .card p { color: red }, оно применится к p внутри scope. Если в проекте BEM используется только ради изоляции, или CSS Modules подключены только для избежания конфликтов имён, @scope часто закрывает задачу без сборочного шага. #frontendvkhub #css #scope