-
Комьюнити VK для фронтендеров. Кто и что стоит за интерфейсами, которыми пользуются миллионы пользователей, — от Почты Mail до VK Teams — рассказываем здесь 🤘
History API создавался для полных перезагрузок страниц. SPA-роутеры годами строили поверх него абстракции: popstate не стреляет при pushState, клики по ссылкам нужно перехватывать вручную, для хеша нужен отдельный слушатель, а history.length даёт только число вместо доступа к стеку.
Navigation API решает это единым событием navigate на window.navigation. К нему приходят клики по ссылкам, сабмиты форм, back/forward и программные переходы — один обработчик вместо россыпи слушателей.
➡️ Роутер на History API:
window.addEventListener("click", (e) => {
const link = e.target.closest("a");
if (!link) return;
e.preventDefault();
history.pushState({}, "", link.href);
render(link.href);
});
window.addEventListener("popstate", () => {
render(location.pathname);
});navigation.addEventListener("navigate", (e) => {
if (!e.canIntercept) return;
e.intercept({
async handler() {
const data = await loadRoute(new URL(e.destination.url));
render(data);
e.scroll();
},
});
});intercept() отключает перезагрузку страницы — DOM обновляет роутер. Условие !canIntercept отсекает переходы, которые нельзя перехватить. Браузер показывает нативный лоадер, пока промис из handler не зарезолвится. e.scroll() восстанавливает прокрутку при back/forward без ручного сохранения координат.history.length — просто число. Navigation API открывает стек через navigation.entries():const prev = navigation
.entries()
.slice(0, navigation.currentEntry.index)
.findLast((e) => new URL(e.url).pathname.startsWith("/list"));
if (prev) navigation.traverseTo(prev.key);
/list в стеке и переходит к нему. На History API для этого нужен параллельный стек на глобальных переменных.id – для привязки кэша, уникальный, не переиспользуетсяkey – для навигации по стеку, переиспользуется при replacegetState() — оно иммутабельно, для обновления нужен updateCurrentEntry().e.signal отменяет незавершённый fetch:e.intercept({
async handler() {
const data = await fetch(url, { signal: e.signal });
if (e.signal.aborted) return;
render(data);
},
});@virtualstate/navigation. React Router и TanStack Router пока остаются на History API, но оба уже обсуждают переход.
Google Search сократил HTML с 107 kB до 60 kB для вернувшихся пользователей. Etsy, Amazon, eBay экономят до 96% на передаче данных.
➡️ Compression Dictionary Transport (RFC 9842) — стандарт HTTP, который переиспользует загруженные ответы как словари сжатия.
#frontendvk #compressiondictionarytransport #http
Разбираем State of HTML 2025, в котором поучаствовали ~6200 респондентов. Главная боль — не нехватка модных API, а невозможность стилизовать <select> и <input type="date">.
#frontendvk #stateofhtml
Делимся результатами ежегодного опроса Devographics о трендах CSS — State of CSS 2025.
Более 5500 респондентов и 50 CSS-фич. :has() — самая используемая (80.4%) и самая любимая (51.5%) фича.
#frontendvk #stateofcss
Разбираем новый отчет от Devographics — ежегодный независимый опрос экосистемы State of React 2025. Данные собирали с ноября 2025 по январь 2026, участие приняли около 3760 разработчиков.
🔵Самая ожидаемая фича: React Compiler
🔵Vite впервые обошёл webpack по использованию
🔵shadcn/ui вплотную приблизился к MUI
🔵Next.js удерживает лидерство по использованию, но теряет удовлетворённость
🔵React Foundation лидирует по позитивным оценкам сообщества
#frontendvk #react #stateofreact
Popover API
Каждый попап на сайте — это исторически минимум 30 строк JS: открыть, закрыть по клику снаружи, поймать Escape, не дать фокусу уйти за пределы, поднять z-index выше всего остального. Popover API делает это нативно. С января 2025 он в Baseline — работает во всех современных браузерах без полифилов.
#frontendvk #javascript #popoverapi
State of JavaScript 2025
Недавно был опубликован State of JS 2025 — ежегодный опрос экосистемы JavaScript от Devographics. В этом году его прошли почти 13 000 разработчиков. Собрали самое важное в карточках.
🔵Самый быстрорастущий инструмент: Vitest и Playwright (+14% год к году)
🔵Самая высокая удовлетворенность: Vite (98% положительных оценок)
🔵Наивысший интерес: Vitest (83%)
🔵Next.js — самый обсуждаемый и самый поляризующий проект
#frontendvk #javascript
@layer — каскадность без !important
Специфичность в CSS всегда была проблемой. Один сторонний компонент с высокой специфичностью, и ты пишешь !important, потом !important поверх !important. @layer делает все это проще.
#frontendvk #css #layer
🔵Chrome 145: column-height и column-wrap
Новые свойства для multi-column layout, которые позволяют строить 2D-потоки с вертикальным заполнением. Пока только Chrome 145+.
🔵focusgroup — нативная клавиатурная навигация
Новый HTML-атрибут focusgroup позволяет делать arrow-key навигацию без ручного управления tabindex — браузер сам перемещает фокус внутри контейнера.
🔵Intl API — форматирование без зависимостейIntl форматирует даты, время, числа, списки и текст с учётом локали прямо в браузере. Никаких лишних килобайт в бандле — всё из коробки.
🔵Сравнение JS-минификаторов
Бенчмарк популярных минификаторов (SWC, oxc-minify, esbuild, terser, uglify-js) по размеру выходного файла и скорости. Полезно при выборе инструмента для production-сборки.
🔵CSS-анимации как state-машина
Запоминание прошедших состояний интерфейса через CSS-анимации: например, был ли элемент в hover. Значение записывается в CSS-переменную через keyframes.
🔵 Диапазон дат на чистом CSS
Подсветка диапазона между двумя датами через :nth-child(N of selector) и :has. Вся визуализация на CSS, JS нужен только для обработки кликов.
📌 Новая статья от инженеров VK на Хабр:
Как мы пережили цветовой кризис в RuStore и нашли путь к тёмной стороне темы
#дайджест #frontendvk
CSS :has() — не parent selector, а условная логика прямо в CSS:has() часто объясняют как «наконец-то parent selector» — можно стилизовать родителя в зависимости от дочернего элемента. Это правда, но это самый скучный способ его использовать. Интереснее то, что :has() по сути добавляет в CSS условную логику, которую раньше можно было выразить только через JavaScript.
В январе 2025 :has() получил статус Baseline Newly Available после исправления бага в iOS Safari — теперь работает во всех современных браузерах. По State of CSS 2025 — один из самых используемых и любимых CSS-селекторов года наравне с Grid и нативным нестингом.
➡️ Блокировка скролла без JS. Классическая задача при открытии модалки — запретить скролл на <body>. Обычно это две строчки JS: добавить класс при открытии, убрать при закрытии. С :has():
body:has(dialog[open]) {
overflow: hidden;
}
<dialog> открыт - скролл заблокирован. Закрыт - разблокирован. Ни строчки JS..gallery:has(.card:hover) .card:not(:hover) {
opacity: 0.5;
filter: grayscale(0.3);
}/* Grid с одной колонкой если элемент один */
.grid:has(> :nth-child(1):last-child) {
grid-template-columns: 1fr;
}
/* Две колонки если элементов два */
.grid:has(> :nth-child(2):last-child) {
grid-template-columns: repeat(2, 1fr);
}
/* Три и больше - три колонки */
.grid:has(> :nth-child(3)) {
grid-template-columns: repeat(3, 1fr);
}
.field:has(input:user-invalid) label {
color: var(--error);
}
.field:has(input:user-invalid) input {
border-color: var(--error);
background: var(--error-bg);
}:user-invalid срабатывает только после того, как пользователь взаимодействовал с полем, а не при загрузке. В связке с :has() — визуальная валидация формы только на CSS.:has() с широкими селекторами вроде body:has(*:hover) или *:has(.active). Браузеру нужно пересчитывать такой селектор при каждом изменении DOM. Чем конкретнее якорный элемент — тот, что стоит до :has(), тем дешевле пересчёт.
CSS Anchor Positioning: позиционирование тултипов без JS
Каждый тултип или дропдаун на странице — это исторически JavaScript.
Popper.js, Floating UI, собственные хелперы на getBoundingClientRect. Причина одна: CSS не умел позиционировать элемент относительно другого произвольного элемента, особенно если они в разных частях DOM. С 2025 года это умеет браузер.
CSS Anchor Positioning работает в Chrome 125+, Firefox 134+ и Safari 18.2+. Для старых браузеров есть полифил от OddBird.
Идея простая: один элемент объявляется якорем через anchor-name, другой привязывается к нему через position-anchor и position-area.
.button {
anchor-name: --my-btn;
}
.tooltip {
position: absolute;
position-anchor: --my-btn;
position-area: top center;
margin-bottom: 8px;
}
position-area — сетка 3×3 вокруг якоря. top center — над якорем по центру. bottom span-right — под якорем с выравниванием вправо. Никакого calc(), никакого getBoundingClientRect.position-try-fallbacks:
.tooltip {
position: absolute;
position-anchor: --my-btn;
position-area: top center;
position-try-fallbacks: bottom center, right span-top, left span-top;
}
<button popovertarget="tip" style="anchor-name: --btn">
Наведи
</button>
<div id="tip" popover style="position-anchor: --btn; position-area: top center">
Подсказка
</div>
@supports (anchor-name: --test) для feature detection.
Moment.js, Day.js и date-fns суммарно скачивают больше 100 миллионов раз в неделю. Три библиотеки, которые существуют только потому, что встроенный Date не справляется с базовыми задачами.
В марте 2026 Temporal достиг Stage 4 и вошёл в ES2026. Теперь это стандарт и вопрос только в том, когда все браузеры догонят.
В чём проблема DateDate смешивает разные сущности в одном объекте: момент времени, локальную дату, парсинг строк, работу с часовыми поясами. Он мутабельный, а арифметика ломается на переходах DST, при сравнении дат и при добавлении дней. В результате целый класс багов живёт в JS-коде десятилетиями.
Как устроен Temporal
Temporal разделяет эти смыслы на отдельные типы, все объекты иммутабельны:
🔵 Temporal.Instant — точный момент с наносекундной точностью, без зоны и календаря;
🔵 Temporal.PlainDate — календарная дата без времени, подходит для дней рождения и дедлайнов;
🔵 Temporal.ZonedDateTime — дата-время с IANA-зоной и календарём, корректно обрабатывает переходы на летнее время.
Типичный сценарий — прибавить две недели:
// Date — мутирует объект
const d = new Date();
d.setDate(d.getDate() + 14);
// Temporal — возвращает новый объект
const now = Temporal.Now.plainDateISO();
const inTwoWeeks = now.add({ days: 14 });
now остался нетронутым, при этом valueOf() у Instant и ZonedDateTime бросает TypeError, т.к. неявное сравнение больше не работает молча.// Date — считать руками
const diffMs = endDate - startDate;
const days = Math.floor(diffMs / 86400000);
const hours = Math.floor((diffMs % 86400000) / 3600000);
// Temporal — готовый Duration
const duration = start.until(end, { largestUnit: "day" });
duration.days; // 1
duration.hours; // 8
duration.total("hours"); // 32
Duration не балансирует автоматически — 100 секунд остаётся PT100S, а не PT1M40S. Если нужна нормализация, то вызывайте .round({ largestUnit: 'hour' }). Это сознательное решение: разработчик видит ровно то, что задал.earlier, later, compatible или reject. Для календарей, бронирований и напоминаний это закрывает целый класс багов.dayOfWeek следует ISO 8601: понедельник — 1, воскресенье — 7, а не 0 и 6 как в Date.getDay()Temporal.PlainDate.compare(), операторы > и < молча возвращают false@js-temporal/polyfill, но он добавляет вес в бандл
Сегодня знакомимся с Андреем Едуновым, фронтенд-техлидом RuStore. Он пришёл в IT ещё во времена модемного интернета, успел из него уйти, пропустить несколько технологических революций и вернуться, чтобы строить крупнейший российский магазин приложений.
➡️ С чего началась твоя карьера в IT?
Как только у меня появился первый компьютер, я сразу в него залип. Тогда только развивался интернет по модему и первые чаты. Помню, увидел, как кто-то пишет цветным текстом, — и подумал: «А что, так можно было?»
Так я узнал про HTML, купил книжку и написал свой первый сайт — максимально простой, конечно. И понял: это моё. Моя первая должность называлась «программист-курьер». Я кодил сайт, а потом на дискетке вёз его деплоить на сервер. Вот такой у меня был пайплайн. Возможно, я был первым в мире девопсом.
➡️ С каким образованием можно стать фронтендером?
У меня высшее геодезическое. Вообще мимо IT. Тогда даже слова «фронтенд» не было — была «вёрстка», а верстальщиков называли недопрограммистами. И я как раз из таких. Но это не помешало мне дорасти до руководителя отдела дизайна и вёрстки в EPAM.
Потом я решил сменить сферу и ушёл из IT на 8 лет. Пока меня не было, в индустрии произошло несколько революций: jQuery, прототипы, Backbone, React, Node.js, сборщики, современные архитектурные подходы. Всё это пришлось догонять практически с нуля.
Возвращение было жёстким: несколько месяцев я буквально жил на JavaScript курсах, чтобы просто вернуться в форму. Дальше — отечественный финтех, зарубежные стартапы, крипта и, конечно, вишенка на торте — RuStore.
➡️ Над чем ты работаешь сейчас?
Я пришёл в RuStore на один внутренний проект, а в итоге успел поработать, кажется, везде. Год провёл в core-команде, где были нестандартные и сложные задачи.
Один из кейсов — собрать каркас нового проекта за неделю. Причём не просто собрать, а сразу заложить хорошую базу с учётом ошибок прошлых проектов: архитектуру, линтеры, договорённости. В итоге это стало основой для новых проектов и материалом для докладов.
Сейчас я в RuStore Консоли — это инструмент, через который разработчики загружают приложения и управляют ими в Store. И именно там я сейчас занимаюсь фичами, которые наши паблишеры давно ждут. Одной из таких фичей стала тёмная тема, про которую я подробно рассказал в статье.
➡️ Чем ты занимаешься вне работы?
На удалёнке начинаешь работать больше, чем в офисе, поэтому без хобби не обойтись. Катаюсь на сноуборде.
А на прошлый день рождения в мою жизнь снова вошла гитара — я играл в детстве, но петь не умел. Сейчас попробовал вновь пойти на вокал — занимаюсь почти год и даже выступил в клубе «16 тонн» на Арбате. Теперь это уже не просто хобби, а отдельная часть жизни.
#frontendvk #команда
🔵Navigation API — теперь кросс-браузерный
Navigation API становится новой основой клиентского роутинга: можно подписываться на изменения URL и перехватывать навигацию через event.intercept, делая адресную строку единым источником состояния без ручной работы с History API.
🔵Новый HTML-атрибут `focusgroup`
Microsoft Edge и Chromium уже поддерживают focusgroup, который нативно управляет клавиатурной навигацией внутри контейнеров — стрелки, пропуск disabled-элементов и восстановление фокуса теперь можно реализовывать без JS-логики.
🔵Claude нашёл 14 критических уязвимостей в Mozilla Firefox
Команда Anthropic использовала Claude для анализа исходников Firefox и обнаружила ранее неизвестные критические баги безопасности — важный сигнал о том, как LLM начинают реально применяться в аудитах сложных кодовых баз.
🔵CodePen 2.0 — переход к новой архитектуре платформы
CodePen готовит обновление с новым компилятором, расширяемой системой блоков и возможной интеграцией AI-инструментов — это может изменить привычный workflow прототипирования интерфейсов прямо в браузере. Обсуждение можно послушать в новом выпуске CodePen Radio.
🔵Opera получила 4 награды iF Design Award за интерфейс браузера
Opera стала самым награждаемым браузером в категории цифровых интерфейсов — заметный тренд на конкуренцию браузеров уже не только на уровне движков, но и UX-слоя вокруг них.
📌 Новые статьи от инженеров VK на Хабр:
🔵О специфике разработки приложений под Smart TV: личный опыт перехода от веба к ТВ
🔵От события до дашборда в облаках: практика по созданию потоковой платформы на Kubernetes
#дайджест #frontendvk
using — JS умеет закрывать ресурсы сам
Каждый раз, когда открываешь соединение, файл или WebSocket — где-то в коде появляется finally. Не потому что хочется, а потому что без него ресурс утечёт, если между открытием и закрытием бросит исключение.
const conn = db.connect();
try {
return conn.query('SELECT ...');
} finally {
conn.close(); // без этого будет утечка при любой ошибке выше
}
using. Работает как const, но при выходе из блока автоматически вызывает [Symbol.dispose]() на объекте, даже если вылетело исключение.
class DbConnection {
[Symbol.dispose]() {
this.conn.close();
}
}
function processData() {
using db = new DbConnection();
return db.query('SELECT ...');
// conn.close() вызовется здесь автоматически
}
await using с [Symbol.asyncDispose]():
async function writeLog() {
await using handle = await openFile('log.txt');
await handle.write('done');
// файл закроется после строки выше, не нужен finally
}
afterEach, и это отдельный блок, который легко забыть или написать неправильно. С using ресурс живёт ровно столько, сколько длится тест:
test('sends request', async () => {
await using server = createMockServer();
// server.close() сам вызовется в конце теста
});
using с версии 5.2. Для браузеров нужен таргет ES2026 или полифил через Symbol.dispose.
jQuery 4.0 — первый за 10 лет мажорный апдейт легендарной библиотеки
jQuery — общеизвестная библиотека, первая версия которой вышла ещё в 2006 году. На протяжении многих лет она являлась первым выбором веб-разработчика для упрощения работы на клиенте.
Ключевые изменения
🔵Удалена поддержка старых браузеров, остались только IE11 и 3-5 последних версий современных браузеров.
🔵Оптимизирован размер бандла. Появилась slim-сборка размером всего 19.5 КБ в gzip.
🔵Появился tree-shaking за счет добавления поддержки ES-модулей.
🔵Поддержка Trusted Types и использование <script> тега для загрузки модулей для предотвращения CSP ошибок.
Сами разработчики jQuery выделяют то, что хотели сделать уже очень давно, а именно удаление:
🔵незадокументированных функций;
🔵внутренних переменных;
🔵устаревших API.
Так были удалены поддержанные в современном JS 13 deprecated-утилит, включая:
🔵jQuery.isArray → Array.isArray();
🔵jQuery.isNumeric → нативные проверки;
🔵jQuery.trim → String.prototype.trim();
🔵jQuery.parseJSON → JSON.parse().
Еще в этой версии jQuery отказались от собственного определения порядка фокуса элементов, которое ранее было необходимо для консистентности в удалённых из поддержки браузерах.
Актуальность
Несмотря на возраст, пакет jQuery по-прежнему получает десятки миллионов загрузок в месяц (70+ млн по npm). А по данным W3Techs он до сих используется более чем на 70% всех веб-сайтов. Особенно явно jQuery оставил свой след в enterprise-сегменте и CMS-решениях (в частности Wordpress).
Как тебе такое, Кирилл?
«Услышав об этом событии, я был искренне удивлен. В весомой части проектов использование jQuery принято избегать и упоминать о библиотеке разве что в контексте мема. Тем не менее сложно отрицать повсеместную распространенность библиотеки, а потому и новость о таком обновлении не видится исключительно смешной и не обоснованной. Более того, вместе с выпуском 4.* версии, разработчики сразу частично охарактеризовали и последующий 5.* апдейт.
Если говорить про характер изменений, то их можно описать, как глобальный рефакторинг с клинкодом, багофиксингом и адаптацией под современные стандарты (esm, csp), которые де факто являются таковыми уже не год и не два. Поэтому возникает вопрос: “Почему только сейчас?”.
Допускаю, что разработчики ждали прохождения своеобразного трешхолда по уменьшению присутствия старых браузеров в мире веба, на что намекает их аккуратный подход по внедрению изменений со словами из описания выпущенного ими релиза: “мы ожидаем, что большинство юзеров смогут обновиться с минимальными изменениями в их коде”. Но разве не разумно оставлять выбор о необходимости поддержки старых браузеров потребителям и параллельно делать то, что делают успешные проекты, а именно активно и своевременно адаптировать инструменты под потребности своего сообщества?
Оценивая потенциальный эффект от обновления, думаю, что это обновление хоть и несколько развязывает руки по использованию jQuery в современных реалиях, но оно НЕ запустит массовую работу над заблокированном техдолгом, потому что если >70% сайтов до сих пор мирится с наличием у себя jQuery, четверть из которых вообще имеет 1 или 2 версию, то вопрос - откуда должна взяться мотивация на переход к 4.* версии, когда разворачивание React/Svelte/Vue/etc в формате SPA и микро-фронтов — это шаблонно и привычно, а отличное от удаления прикосновение к jQuery в отдельных проектах не иначе как моветон», — рассказал Кирилл Радыгин, руководитель Web платформы Одноклассников.