fa
Feedback
Логово верстальщика

Логово верстальщика

رفتن به کانال در Telegram

Логово верстальщиков: HTML, CSS, JavaScript, практики современной верстки, вайбкодинг и использование ИИ в разработке. Личный блог автора - @just_genych По вопросам рекламы или разработки: @g_abashkin

نمایش بیشتر
8 247
مشترکین
+424 ساعت
-147 روز
+7030 روز

در حال بارگیری داده...

جذب مشترکین
ژوئن '26
ژوئن '26
+125
در 0 کانال‌ها
مه '26
+419
در 22 کانال‌ها
Get PRO
آوریل '26
+109
در 0 کانال‌ها
Get PRO
مارس '26
+77
در 0 کانال‌ها
Get PRO
فوریه '26
+63
در 0 کانال‌ها
Get PRO
ژانویه '26
+192
در 57 کانال‌ها
Get PRO
دسامبر '25
+116
در 25 کانال‌ها
Get PRO
نوامبر '25
+207
در 69 کانال‌ها
Get PRO
اکتبر '25
+74
در 28 کانال‌ها
Get PRO
سپتامبر '25
+115
در 85 کانال‌ها
Get PRO
اوت '25
+38
در 0 کانال‌ها
Get PRO
ژوئیه '25
+62
در 2 کانال‌ها
Get PRO
ژوئن '25
+63
در 9 کانال‌ها
Get PRO
مه '25
+210
در 4 کانال‌ها
Get PRO
آوریل '25
+113
در 0 کانال‌ها
Get PRO
مارس '25
+191
در 21 کانال‌ها
Get PRO
فوریه '25
+120
در 3 کانال‌ها
Get PRO
ژانویه '25
+299
در 18 کانال‌ها
Get PRO
دسامبر '24
+440
در 53 کانال‌ها
Get PRO
نوامبر '24
+427
در 40 کانال‌ها
Get PRO
اکتبر '24
+409
در 27 کانال‌ها
Get PRO
سپتامبر '24
+389
در 33 کانال‌ها
Get PRO
اوت '24
+361
در 24 کانال‌ها
Get PRO
ژوئیه '24
+361
در 34 کانال‌ها
Get PRO
ژوئن '24
+328
در 32 کانال‌ها
Get PRO
مه '24
+345
در 28 کانال‌ها
Get PRO
آوریل '24
+328
در 27 کانال‌ها
Get PRO
مارس '24
+277
در 10 کانال‌ها
Get PRO
فوریه '24
+653
در 3 کانال‌ها
Get PRO
ژانویه '24
+392
در 24 کانال‌ها
Get PRO
دسامبر '23
+696
در 34 کانال‌ها
Get PRO
نوامبر '23
+348
در 16 کانال‌ها
Get PRO
اکتبر '23
+629
در 7 کانال‌ها
Get PRO
سپتامبر '23
+61
در 0 کانال‌ها
Get PRO
اوت '23
+17
در 0 کانال‌ها
Get PRO
ژوئیه '23
+21
در 0 کانال‌ها
Get PRO
ژوئن '23
+11
در 0 کانال‌ها
Get PRO
مه '23
+13
در 0 کانال‌ها
Get PRO
آوریل '23
+17
در 0 کانال‌ها
Get PRO
مارس '23
+9
در 0 کانال‌ها
Get PRO
فوریه '23
+16
در 0 کانال‌ها
Get PRO
ژانویه '23
+16
در 0 کانال‌ها
Get PRO
دسامبر '22
+18
در 0 کانال‌ها
Get PRO
نوامبر '22
+23
در 0 کانال‌ها
Get PRO
اکتبر '22
+21
در 0 کانال‌ها
Get PRO
سپتامبر '22
+83
در 0 کانال‌ها
Get PRO
اوت '22
+23
در 0 کانال‌ها
Get PRO
ژوئیه '22
+22
در 0 کانال‌ها
Get PRO
ژوئن '22
+10
در 0 کانال‌ها
Get PRO
مه '22
+19
در 0 کانال‌ها
Get PRO
آوریل '22
+18
در 0 کانال‌ها
Get PRO
مارس '22
+20
در 0 کانال‌ها
Get PRO
فوریه '22
+19
در 0 کانال‌ها
Get PRO
ژانویه '22
+13
در 0 کانال‌ها
Get PRO
دسامبر '21
+11
در 0 کانال‌ها
Get PRO
نوامبر '21
+15
در 0 کانال‌ها
Get PRO
اکتبر '21
+26
در 0 کانال‌ها
Get PRO
سپتامبر '21
+27
در 0 کانال‌ها
Get PRO
اوت '21
+58
در 0 کانال‌ها
Get PRO
ژوئیه '21
+32
در 0 کانال‌ها
Get PRO
ژوئن '21
+62
در 0 کانال‌ها
Get PRO
مه '21
+19
در 0 کانال‌ها
Get PRO
آوریل '21
+37
در 0 کانال‌ها
Get PRO
مارس '21
+44
در 0 کانال‌ها
Get PRO
فوریه '21
+61
در 0 کانال‌ها
Get PRO
ژانویه '21
+53
در 0 کانال‌ها
Get PRO
دسامبر '20
+7 999
در 0 کانال‌ها
تاریخ
رشد مشترکین
اشارات
کانال‌ها
24 ژوئن+4
23 ژوئن+13
22 ژوئن+1
21 ژوئن+1
20 ژوئن+3
19 ژوئن+1
18 ژوئن+1
17 ژوئن0
16 ژوئن+2
15 ژوئن0
14 ژوئن+1
13 ژوئن+1
12 ژوئن+5
11 ژوئن+40
10 ژوئن+23
09 ژوئن+17
08 ژوئن+1
07 ژوئن+1
06 ژوئن0
05 ژوئن+2
04 ژوئن+1
03 ژوئن+2
02 ژوئن+4
01 ژوئن+1
پست‌های کانال
Selectmenu с appearance: base-select: кастомные выпадающие списки без React Select или Select2 Кастомизация нативного select — вечная головная боль. JS-библиотеки тащат десятки килобайт кода и ломают доступность. Решение появилось в Chrome 133+: appearance: base-select — это CSS-свойство, превращающее стандартный select в полностью стилизуемый компонент без единой строчки JavaScript. Главная ошибка — продолжать использовать appearance: none и вручную эмулировать поведение выпадашки. Что даёт base-select appearance: base-select меняет модель отображения: option-элементы становятся стилизуемыми, появляются псевдоэлементы ::picker(select) (сам попап) и ::picker-icon (стрелка), а также псевдоклассы :open и :closed. В production-контексте — фильтры в админке, формы e-commerce, поиск по категориям — больше не нужен React Select или Select2. Пример production-решения для фильтра:
select {
  appearance: base-select;
  padding: 8px 12px;
  border: 1px solid #c0c0c0;
  border-radius: 4px;
}
select::picker(select) {
  border: 1px solid #888;
  box-shadow: 0 2px 8px rgba(0,0,0,0.1);
  max-height: 200px;
}
option:hover {
  background: #f0f0f0;
}
select::picker-icon {
  content: '▼';
  font-size: 10px;
  color: #666;
}
Практический совет: используйте :open для стилей при открытом попапе — например, закругление рамки только сверху. Ключевые ограничения и trade-offs — Поддержка: только Chrome 133+ (в Firefox и Safari пока нет). Для production, где важна кросc-браузерность, — используйте как progressive enhancement. — Не путать с appearance: none: base-select сохраняет нативное поведение и доступность. — optgroup стилизуется через $lt;optgroup$gt; — жирный шрифт для подзаголовков. — Нельзя заменить option на произвольные HTML-элементы: это trade-off в пользу семантики и ARIA. Предупреждение: не пытайтесь применять base-select для сложных компонентов с поиском или мультивыбором — для них всё ещё нужен JS. Типичная ошибка — стилизация ::picker(select) через position: absolute; это сломает выравнивание в layout. Когда использовать в production Подходит для микро-интерфейсов: фильтры, формы обратной связи, кастомизация select в дизайн-системах. Если проект на чистом HTML/CSS — zero-dependency решение. Не подходит, если нужны Firefox/Safari или кастомная отрисовка option с иконками. Вывод: appearance: base-select — это сдвиг парадигмы: нативный select получает кастомизацию без JS, но с инженерным trade-off — только для Chromium, где стабильность layout и доступность важнее универсальной браузерной поддержки.

2
ИИ vs ЧЕЛОВЕК / AI УЖЕ МНОГОЕ УМЕЕТ, НО НЕ ТАК КАК ТЫ ... Нейросети уже пишут, рисуют и отвечают 24/7. Это мощно, и мы за про
ИИ vs ЧЕЛОВЕК / AI УЖЕ МНОГОЕ УМЕЕТ, НО НЕ ТАК КАК ТЫ ... Нейросети уже пишут, рисуют и отвечают 24/7. Это мощно, и мы за прогресс. Но есть вещи, которые алгоритмы никогда не заменят: — эмпатию к клиенту — доверие, которое строится годами — продажи без манипуляций, с душой ⚠️ Технологии — это инструмент, а главное — это ты и твой живой контакт. Приглашаем тебя в ЭКО-Пространство, где технологии — это фон, а главное — это ты и твой клиент ✔️ В этой ПОДБОРКЕ есть кое-что поважнее алгоритмов — ДОВЕРИЕ. В папке собраны каналы про экологичные продажи, про понимание, про рост без выгорания. Пусть ИИ пишет тексты, а ты учись создавать отношения. 💚 Добавляй папку в свой актив и делись с друзьями! 📌 Ссылка ➡️ https://t.me/addlist/9wQJPILNMKNkNmNk 👉 Делимся знаниями и аудиторией — растём вместе ⚡️
218
3
⁣CSS anchor-positioning: тултипы с привязкой к контенту без ResizeObserver Долгое время позиционирование тултипов и попапов в production было упражнением в ручных вычислениях: ResizeObserver, scroll-обработчики, проверки clientWidth и clientHeight, постоянное пересчитывание offset-ов при ресайзе. Особенно больно это в таблицах, где каждая ячейка может стать якорем, или в кастомных тултипах с адаптивными данными. CSS anchor-positioning переносит эту логику в браузер. Механика: anchor-name и position-anchor Элементу-якорю задаём anchor-name: --tooltip. Тултипу — position-anchor: --tooltip и inset-area для указания стороны появления. Если не хватает места, position-try-options: flip-block автоматически переворачивает блок без ручной проверки на overflow. Пример: .anchor { anchor-name: --tooltip; } .tooltip { position: fixed; position-anchor: --tooltip; inset-area: block-end; position-try-options: flip-block; } Тултип едет за якорем при скролле, подстраивается под viewport. Размеры можно привязать через anchor-size(), например для ширины на весь блок. Где пригодится, а где нет Подходит для tooltip, popover в таблицах, кастомных dropdown-меню с динамическим контентом. Отлично сочетается с popover — браузер сам закрывает по клику вне и по Escape. Не использовать, если проект на старых браузерах (поддержка: Chrome 125+, Safari 18+ с флагом, Firefox в разработке). Также не подходит для сложных кастомных анимаций — position-try-options не работает с transition, только через CSS animation. Типичная ошибка и предупреждение Ошибка: считать, что подойдёт для всех кейсов. Например, если внутри тултипа есть скроллящийся контент, при flip могут возникать баги с позиционированием. Обязательно тестируйте на реальных данных, особенно на мобильных устройствах. Ещё один trade-off — нет колбэков для кастомной логики, что важно для a11y: например, управление фокусом требует дописывания JS. Вывод: anchor-positioning убирает ручной расчёт координат и makes адаптивные тултипы предсказуемыми, но не заменяет JS полностью — a11y и сложные анимации пока требуют гибридного подхода.
192
4
Gemini vs ChatGPT: СМЕНА ФАВОРИТОВ ... вот что вышло 👇 * Все вокруг обсуждают ChatGPT, а я нашел альтернативу, которая реаль
Gemini vs ChatGPT: СМЕНА ФАВОРИТОВ ... вот что вышло 👇 * Все вокруг обсуждают ChatGPT, а я нашел альтернативу, которая реально качает — Gemini от Google. Пользуюсь и очень доволен. Почему стоит попробовать: ✔️ Бесплатно (базовая версия) ✔️ Контекст 2 млн токенов — загружайте хоть целые кодобазы ✔️ Понимает текст, картинки, видео и аудио ✔️ Дружит с Google Диском, Gmail и календарем ✔️ Код пишет на уровне топ-моделей Решил проверить его в деле — и не прогадал. Попросил Gemini найти для меня экспертные каналы по IT и AI, чтобы собрать чистое инфополе с нуля и не делать все вручную. Закинул ссылки на проверенных авторов, и нейросеть сама проанализировала сотни рекомендаций, отсеяв пустышки. Результат — готовая подборка из 20+ каналов с реальным опытом по: AI-воркфлоу, автоматизации, вайб-кодингу, промт-инжинирингу, RAG-системам, нейрогенерации, крипте и др. 🔗 Забирайте список в один клик 👇 https://t.me/addlist/9wQJPILNMKNkNmNk * Пишите в комменты — пробовали Gemini? Делитесь с друзьями впечатлениями и добавляйте подборку в свой актив 📌
295
5
⁣content-visibility: auto и contain-intrinsic-size: рендеринг только в видимой зоне без JS Каталог на 500 товаров, лента блога или таблица лидеров — браузер честно считает layout и рисует каждый элемент, даже скрытый под скроллом. Результат: дёрганый скролл, высокий LCP и плавающий CLS. Многие идут в React Virtual, но для статичных списков есть решение на чистом CSS. Как работают два свойства content-visibility: auto откладывает рендеринг, layout и paint элементов за пределами viewport. Браузер не тратит CPU на скрытые карточки. Но если не задать размер — полоса прокрутки сжимается, CLS растет. Решает contain-intrinsic-size: он резервирует место для скрытого блока, имитируя его размер. Пример для карточки товара в каталоге: .product-card { content-visibility: auto; contain-intrinsic-size: 320px 480px; } Браузер на первом рендере отрисовывает только видимые карточки, а для скрытых резервирует 320x480px. Скролл стабильный, LCP снижается, layout не пересчитывается. Где реально пригодится Повторяющиеся карточки в e-commerce, лендинги с секциями, FAQ-аккордеоны, таблицы с десятками строк, бесконечные ленты без lazy-load. Для SSR и статичных страниц — идеально: никакой зависимость от JS. Типичные ошибки и ограничения * Не вешай на шапку сайта, первый экран или уникальные блоки — свойство только для повторяющихся элементов вне viewport. * Анимации при появлении (Intersection Observer) могут не сработать — проверь отрисовку. * Firefox поддерживает с 128 версии, Safari — частично. На production проверь через caniuse или используй как progressive enhancement. content-visibility: auto — не замена виртуализации с динамической подгрузкой. Но для статичных списков это бесплатный буст LCP и CLS без JS. Вывод: Используйте content-visibility: auto с contain-intrinsic-size для повторяющихся элементов — это даёт стабильный CLS и быстрый первый рендер без фреймворков.
317
6
⁣Container Queries: Резиновые карточки товаров без медиазапросов по viewport Карточки товаров, которые адаптируются к ширине родителя, а не экрана — это @container. Эта техника незаменима в каталогах, сайдбарах и динамических сетках e-commerce, где одна и та же карточка может отображаться в колонке, группе или flex-ленте. Частая ошибка — пытаться подстроить компонент под разные контексты через медиазапросы по ширине экрана, что ведет к раздутым стилям и нестабильному layout. Как это работает Суть: вы пишете container-type: inline-size на родителе. И внутри карточки используете @container (max-width: 400px) { ... }. Стили сработают, когда сам контейнер сузится до указанной ширины, а не когда окно браузера станет меньше. Это дает предсказуемость: карточка адаптируется под своё непосредственное окружение. Production-ready пример с grid Родительская сетка: .cards-grid { container-type: inline-size; display: grid; grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); gap: 20px; } А внутри самой карточки — контейнерные запросы: .product-card { display: flex; flex-direction: column; gap: 12px; } @container (min-width: 350px) { .product-card { flex-direction: row; } } @container (max-width: 300px) { .product-description { display: none; } .product-price { font-size: 14px; } } Что это даёт. Карточка подстраивается внутри любого родителя — будь то колонка, сайдбар или flex-лента. Вам не нужно перебирать брейкпоинты под каждую секцию. Контент сам решает, когда менять раскладку. Практический совет и предупреждение об ошибке Типичная ошибка: заводить контейнер на body или глобальном wrapper. Это убивает смысл — запросы всё равно привязываются к viewport. Мой совет: заводите контейнер на уровне секций. Так проще контролировать вложенность и рендеринг быстрее. Из trade-offs: @container работает в Chrome 105+ и Safari 16+. Для старых браузеров придётся вешать фолбэк на @media. И да, aspect-ratio внутри контейнера пока не совместим — баг редкий, но встречается в сложных layout-сетках. Вывод: Container Queries делают компоненты по-настоящему автономными — вместо адаптации к экрану они адаптируются к контексту, что повышает стабильность интерфейса и упрощает поддержку дизайн-систем.
264
7
⁣Inertial scrolling и overscroll-behavior: управляем pull-to-refresh, bounce-эффектами и sticky-навигацией без js-костылей На iOS при свайпе внутри модалки за ней летит весь сайт. На Android sticky-шапка выезжает за край экрана. Виноват inertial scrolling с bounce и pull-to-refresh. Раньше лечили -webkit-overflow-scrolling и touchmove.preventDefault(). Сейчас есть overscroll-behavior — одно CSS-свойство без скриптов, решающее проблему в лендингах, кабинетах и дизайн-системах. Как работает Три значения: auto — поведение по умолчанию с bounce; contain — эффект внутри контейнера не выходит наружу; none — полное отключение bounce и pull-to-refresh. Важный нюанс — каскад: если у дочернего контейнера contain, родитель не получит лишнего скролла, даже когда дочерний уперся в границу. Примеры в production Фиксируем sticky-хедер, чтобы не выскальзывал: .sticky-header { position: sticky; top: 0; overscroll-behavior: contain; } Отключаем pull-to-refresh на модалке: .modal-content { overscroll-behavior: contain; overflow-y: auto; } Изолируем всю страницу от bounce: html { overscroll-behavior: none; } Почему это лучше JS Производительность: не нужен touchmove с preventDefault(). Доступность: инерция внутри контейнера сохраняется, навигация снаружи не ломается. Подходит для SSR и статики — никаких скриптов. Типичная ошибка На iOS при overscroll-behavior: none bounce внутри элемента отключается. Если нужно сохранить инерцию внутри, но убрать на уровне страницы — ставьте contain на контейнере. Для sticky-элементов комбинируйте с contain на родителе, иначе навигация будет дергаться. Вывод: overscroll-behavior — инженерный инструмент для модалок, дро-даунов, sticky-шапок и pull-to-refresh, избавляющий от JS-костылей и обеспечивающий стабильность layout.
305
8
⁣Overflow:clip и scrollbar-gutter: стабильные макеты без выпадения контента при появлении скроллбара Замечали: страница грузится, контент уже почти встал, и тут появляется скроллбар — весь блок уезжает вправо на 16 пикселей. Или наоборот: открываешь модалку, body получает overflow:hidden, и макет дёргается. В production — от лендингов до админок — это ломает центровку, сдвигает заголовки и портит UX. Частая ошибка: ставят overflow:hidden и ждут стабильности, забывая, что он создаёт новый контекст скролла. Overflow:clip Свойство не резервирует место под скроллбар вообще. Контент не вылезет наружу, но и программный скроллинг не сработает. Годится для декоративных блоков, слайдеров, попапов — где скролла не будет никогда. Минимум сюрпризов, но не подходит для динамических списков. Scrollbar-gutter: stable Вот это уже ближе к повседневной верстке. Свойство говорит браузеру: оставь место под скроллбар, даже когда его нет. Ширина контента не прыгает ни при загрузке, ни при открытии модалки поверх: .container { overflow-y: auto; scrollbar-gutter: stable; } Для симметрии (например, в карточках по центру) используйте stable both-edges. Trade-off и нюанс - clip — для элементов без скролла (слайдеры, декоративные врапперы). - stable — для списков, таблиц, лент. - Но Safari (WebKit) scrollbar-gutter пока не поддерживает. Придётся вручную резервировать ширину через padding-right под скроллбар — проверяйте поведение в DevTools на вкладке Rendering. Вывод: Связка overflow:clip и scrollbar-gutter:stable даёт предсказуемый layout без костылей для большей части браузеров, но требует fallback для WebKit.
308
9
⁣CSS light-dark(): адаптивная тёмная тема без дублирования цветов В production-проектах — лендингах, админ-панелях, дизайн-системах — тёмная тема часто реализуется через @media (prefers-color-scheme: dark) с полным копированием всех переменных. Одна правка в светлой — и приходится синхронизировать тёмную. Это ломает переиспользуемость и стабильность интерфейса. Как это работает Функция light-dark() принимает два аргумента: первый — цвет для светлой темы, второй — для тёмной. Но без color-scheme на :root она не знает контекста. :root { color-scheme: light dark; } body { background: light-dark(white, black); color: light-dark(black, white); } Ключевой момент: color-scheme можно выставить вручную — например, color-scheme: dark — и функция вернёт тёмное значение вне зависимости от system preference. Это даёт гибкость для тестирования или ручного переключения. Устранение дублирования переменных Раньше приходилось писать два набора переменных под медиа-запросом. Теперь — один: :root { --bg: light-dark(white, black); --text: light-dark(black, white); } Типичная ошибка — забыть, что light-dark() работает только с цветами. url() или auto внутри не поддерживаются. Для градиентов — да, для ссылок — нет. Trade-offs и fallback Поддержка: Chrome 123+, Safari 17.2+, Firefox 130+. Для старых браузеров обязателен fallback: background: white; background: light-dark(white, black); Советую внедрять в свежих проектах, где тёмная тема — полноценная схема, а не пара перекрашенных блоков. Код становится предсказуемее, а visual regression снижается. Вывод: light-dark() избавляет от дублирования цветовых переменных в тёмной теме, но требует контроля color-scheme и fallback для старых браузеров.
325
10
⁣CSS scroll-timeline: индикатор подписи внутри sticky без IntersectionObserver Знакомый сценарий: sticky-блок с проверкой подписи. Внутри progress-бар, который должен заполняться, пока юзер скроллит секцию. Раньше — только IntersectionObserver или кастомные скролл-слушатели, которые дёргают layout. Теперь можно без JS на compositor-потоке. Свойство animation-timeline привязывает анимацию к прогрессу скролла родителя. Вместо времени — проскролленные пиксели. Для sticky это работает напрямую: блок висит, индикатор растёт, пока контент под ним прокручивается. Типичная ошибка — думать, что нужен JS для каждого пикселя прогресса. Как работает @keyframes fill { from { width: 0%; } to { width: 100%; } } .sticky-indicator { position: sticky; top: 0; height: 4px; background: lightgray; } .sticky-indicator::after { content: ''; display: block; height: 100%; background: #4caf50; animation: fill 1s linear; animation-timeline: scroll(block nearest); } Разбор: scroll(block nearest) привязывается к ближайшему скроллящемуся предку по блочной оси. ::after меняет ширину пропорционально скроллу. Никаких событий скролла и throttle. Почему это удобно * Анимация в compositor-потоке, без рывков на main thread. * Работает внутри любого sticky: хедер, сайдбар, панель подписи. * Не требует инициализации или подписки на DOM — предсказуемо с первого кадра. Ограничения и trade-offs Пока только Chromium (103+). Firefox за флагом layout.css.scroll-driven-animations.enabled. Safari в процессе. Если нужно несколько индикаторов на разных контейнерах — каждый получает свою timeline, что исключает гонки данных. Production-кейсы: лендинги с длинным описанием подписи, страницы документации со sticky-оглавлением. Везде, где раньше ставил IntersectionObserver и вручную обновлял ширину. Предупреждение: не используй scroll(block nearest) без overflow-родителя — timeline не сработает, и анимация останется на старте. Вывод: Scroll-driven animations — это не хайп, а способ выкинуть JS из рендеринга для sticky-индикаторов, давая стабильный layout без дёрганий на мобильных устройствах.
303
11
View Transitions API — SPA-подобные переходы без единой строчки JS Раньше плавная навигация между страницами требовала сборку
View Transitions API — SPA-подобные переходы без единой строчки JS Раньше плавная навигация между страницами требовала сборку на SPA или извращения с fetch и ручной заменой DOM. View Transitions API ломает это правило — браузер сам снимает снэпшоты и анимирует переходы на обычном HTML/CSS. Частая ошибка: полагать, что MPA не может быть плавной, и тянуть тяжелый фреймворк для простого лендинга или статического сайта. Базовый запуск — мета-тег Просто добавьте в <head>: <meta name="view-transition" content="same-origin" />. Все переходы по ссылкам внутри домена получат дефолтную кросс-фейд анимацию. Без импортов, без сборки. Поддержка: Chrome 111+, Safari 18.2+. Firefox — в разработке, используйте как progressive enhancement. Кастомные анимации через CSS Переопределите псевдоэлементы ::view-transition-old() и ::view-transition-new(). Production-пример: плавный скейл и сдвиг для списка статей на медиа-сайте: ::view-transition-old(root) { animation: fade-out 0.3s ease-out; } ::view-transition-new(root) { animation: fade-in 0.3s ease-in; } @keyframes fade-out { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0.9); } } @keyframes fade-in { from { opacity: 0; transform: scale(0.9); } to { opacity: 1; transform: scale(1); } } Практический совет: если анимация дергается, проверьте contain: paint; на контейнере — это стабилизирует layout. Типичная ошибка: забыть про prefers-reduced-motion — браузер сам вырубает эффекты, но для кастомных анимаций лучше добавить обертку @media (prefers-reduced-motion: no-preference). Связывание элементов между страницами Используйте view-transition-name, чтобы анимировать конкретный компонент. Например, карточка товара на каталоге: .card { view-transition-name: product-card; } На странице товара — то же имя на блоке деталей. Браузер сопоставит их и анимирует трансформацию. Инженерный trade-off: это ломается, если имена не уникальны на странице или если элементы меняют DOM-позицию при ресайзе. Всегда тестируйте на адаптивных макетах. Вывод: View Transitions API — это инструмент для постепенного улучшения, который даёт нативную плавность MPA без оверхэда SPA, сохраняя семантику, доступность и простоту статической верстки.
339
12
text-wrap: balance и text-box-trim: точный контроль многострочного текста без кастомных JS-обрезок и magic-отступов Странное
text-wrap: balance и text-box-trim: точный контроль многострочного текста без кастомных JS-обрезок и magic-отступов Странное чувство, когда в Figma заголовок выглядит ровно, а в браузере последняя строка уходит в одну букву. Или когда карточка с текстом плывёт из-за того, что line-height даёт лишние отступы сверху и снизу. Это частая проблема в лендингах, кабинетах и дизайн-системах, где каждый пиксель важен для предсказуемости layout. Раньше с этим воевали костылями. JS-скрипты, которые считают ширину строк и вставляют <br>. Или обнуление line-height с ручным подбором margin. В 2024 году можно проще. Балансировка строк без JS text-wrap: balance берёт на себя балансировку строк. Браузер сам решает, где разорвать текст, чтобы все строки были примерно одинаковой длины. Особенно видно на заголовках из 2-4 строк. Классический пример — блок .title { text-wrap: balance; }. Но не кидайте это на параграфы. На длинных текстах браузер будет считать разрывы для каждой строки, и нагрузка растёт. Ошибка: вешать на все блоки подряд — теряете производительность на пересчёте. Удаление магических отступов Вторая штука — text-box-trim. Пока экспериментальная, работает в Safari и Chrome Canary. Убирает те самые «магические» отступы, которые line-height добавляет над первой строкой и под последней. Раньше приходилось выставлять отрицательный margin с дробными значениями. Теперь: text-box-trim: both; text-box-edge: cap alphabetic;. Полезно в карточках, кнопках, списках — сетка перестаёт плыть. Совет: тестируйте в production на проектах с поддержкой только современных браузеров. Trade-offs и поддержка Минусы: balance не стоит вешать на всё подряд, только на короткие блоки. text-box-trim пока не завезли в стабильные браузеры. Но спецификация движется, и через пару лет это будет базой. Предупреждение: не используйте balance на hero-секциях с большим текстом — браузер может подтормаживать на мобильных. Вывод: Использование text-wrap: balance для заголовков и text-box-trim для точного выравнивания текста в layout-сетках — это шаг к стабильному и предсказуемому дизайну без хаков.
374
13
field-sizing: content — авто-высота input и textarea без единой строчки JS Раньше динамическую высоту полей ввода подстраивал
field-sizing: content — авто-высота input и textarea без единой строчки JS Раньше динамическую высоту полей ввода подстраивали через scrollHeight и обработчики событий. В production — формы обратной связи, комментарии в лендингах, кабинетах, SaaS-интерфейсах — это приводило к лишним рекалькуляциям и багам с resize. Частая ошибка — забывать сбрасывать замеры при скрытии полей или анимациях. Как работает field-sizing: content заставляет браузер сам рассчитывать высоту по содержимому. Никакого JS, только CSS: textarea, input[type="text"] { field-sizing: content; min-height: 2em; max-height: 10em; } Свойство применимо к textarea и текстовым input (text, email, search, tel, url, password). select не поддерживается. Важно: resize при этом не ломается, пользователь всё ещё может растягивать поле. Production-применение Поля обратной связи, комментарии, списки задач — везде, где контент непредсказуем. Особенно ценно в design systems и компонентных библиотеках, где высоту нельзя захардкодить. Вместо ResizeObserver и багов с размерами под скроллингом — предсказуемый layout. Типичная ошибка Полагаться только на field-sizing без ограничителей. Если контент длинный, поле заметно сдвинет соседние элементы. Всегда ставь min-height и max-height, чтобы контролировать стабильность интерфейса и избежать внезапного визуального регресса. Trade-offs JS всё ещё нужен для анимации расширения, поддержки старых браузеров (Firefox пока частично) или кастомных элементов. Но для типовых полей — это шаг к CSS как инструменту поведения, а не только оформления. Вывод: Инженерный takeaway — field-sizing: content убирает связку JS + scrollHeight, делая адаптивную высоту предсказуемой и поддерживаемой в любой сторонке.
404
14
100vh на мобильных все еще ломает full-screen интерфейсы: dvh/svh/lvh и safe-area для CTA без обрезания В лендингах, paywall,
100vh на мобильных все еще ломает full-screen интерфейсы: dvh/svh/lvh и safe-area для CTA без обрезания В лендингах, paywall, onboarding, SaaS-кабинетах и дизайн-системах первый экран часто должен занять видимую высоту. Типичная ошибка - ставить height: 100vh и получать CTA под нижней панелью браузера или home indicator. Единицы viewport • 100svh - высота при раскрытых панелях браузера: безопасно, но может дать запас. • 100lvh - высота при скрытых панелях: годится для фона, рискованна для контента. • 100dvh - текущая видимая высота: хороший выбор для экранов состояния. • env(safe-area-inset-bottom) - защита CTA от системных зон. Production-паттерн .screen{ min-height:100vh; min-height:100svh; min-height:100dvh; display:flex; flex-direction:column; padding:max(16px,env(safe-area-inset-top)) 16px max(16px,env(safe-area-inset-bottom)); } .screen__content{flex:1;min-height:0} .screen__cta{position:sticky;bottom:max(16px,env(safe-area-inset-bottom))} Trade-off dvh пересчитывается при изменении browser chrome и может давать визуальные скачки. Не вешайте height: 100dvh на длинные страницы: для hero, modal screen и onboarding чаще безопаснее min-height, чтобы контент мог вырасти. Практическое правило • full-screen состояние - min-height: 100dvh • максимально стабильный первый экран - 100svh • декоративный фон - можно lvh • нижний CTA - padding/bottom через max(..., env(safe-area-inset-bottom)) Вывод: Мобильная верстка больше не сводится к 100vh: выбирайте viewport-единицу под сценарий и всегда резервируйте safe-area.
440
15
CSS Subgrid в продакшене: выравниваем вложенные формы и карточки без дублирования сеток В кабинетах, checkout, SaaS-формах и
CSS Subgrid в продакшене: выравниваем вложенные формы и карточки без дублирования сеток В кабинетах, checkout, SaaS-формах и дизайн-системах часто нужно, чтобы вложенные секции попадали в колонки общего layout. Типичная ошибка - копировать grid-template-columns в каждый компонент и потом ловить рассинхрон на адаптиве. Где помогает subgrid subgrid позволяет вложенному grid использовать треки непосредственного родителя по колонкам или строкам. Это полезно, когда форма разбита на группы, но label, input и action-кнопки должны стоять по одной визуальной сетке. .settings { display: grid; grid-template-columns: 160px minmax(0, 1fr) max-content; gap: 12px 16px; } .group { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; gap: inherit; } .group > input { grid-column: 2; min-width: 0; } Production-смысл Так сетка описывается один раз в контейнере, а вложенные формы, строки настроек или карточки наследуют ее геометрию. Меньше CSS-дублей - меньше риска, что после изменения breakpoint'а кнопки, поля и подписи начнут визуально разъезжаться. Хорошие кандидаты: • большие формы настроек • checkout и billing flows • dashboard-строки со сложной вложенностью • карточки в дизайн-системе с общей action-зоной Ограничение, о котором часто забывают subgrid работает только от непосредственного grid-контейнера, а сам элемент должен быть grid item и занимать нужный диапазон: .card { display: grid; grid-column: 1 / -1; grid-template-columns: subgrid; } Недостаточно просто написать grid-template-columns: subgrid внутри любого компонента - без родительской grid-сетки он не даст ожидаемого выравнивания. Практический паттерн Используйте subgrid там, где цена дублирования сетки реальна, и держите предсказуемый fallback: .form-group { display: grid; gap: 12px; } @supports (grid-template-columns: subgrid) { .form-group { grid-column: 1 / -1; grid-template-columns: subgrid; } } Fallback может быть обычной вертикальной формой. Интерфейс не ломается, а современные браузеры получают точное выравнивание. Вывод: subgrid стоит тащить в продакшен не ради синтаксиса, а там, где он снижает дублирование CSS и повышает стабильность сложного layout.
373
16
CSS Scroll-Driven Animations уже можно аккуратно тащить в продакшен — особенно для двух задач, где раньше часто ставили Inter
CSS Scroll-Driven Animations уже можно аккуратно тащить в продакшен — особенно для двух задач, где раньше часто ставили IntersectionObserver или scroll-listener: 1. reading progress bar 2. sticky-reveal секции Главная идея: прогресс анимации привязан не ко времени, а к скроллу. animation-timeline: scroll(root block); или к появлению конкретного блока во viewport: animation-timeline: --section; animation-range: entry 15% cover 45%; То есть браузер сам считает progress timeline, а мы описываем только CSS-анимацию. Пример: progress bar + sticky-reveal без JS <div class="read-progress"></div> <section class="feature"> <div class="feature__sticky"> <h2>Scroll-driven reveal</h2> <p>Блок раскрывается по мере прохождения секции.</p> </div> </section> .read-progress { position: fixed; z-index: 1000; inset: 0 0 auto; block-size: 3px; background: #7c3aed; transform-origin: 0 50%; transform: scaleX(0); display: none; } .feature { min-block-size: 180vh; } .feature__sticky { position: sticky; top: 20svh; opacity: 1; transform: none; } @supports (animation-timeline: scroll()) { .read-progress { display: block; animation: progress linear both; animation-timeline: scroll(root block); } @keyframes progress { to { transform: scaleX(1); } } .feature { view-timeline-name: --feature; view-timeline-axis: block; } .feature__sticky { opacity: 0; transform: translateY(24px) scale(.98); animation: stickyReveal linear both; animation-timeline: --feature; animation-range: entry 15% cover 45%; will-change: opacity, transform; } @keyframes stickyReveal { to { opacity: 1; transform: none; } } @media (prefers-reduced-motion: reduce) { .read-progress, .feature__sticky { animation: none; } .feature__sticky { opacity: 1; transform: none; } } } Что важно для продакшена: • Заворачивать в @supports Scroll-Driven Animations пока не стоит считать безусловным baseline. Делайте progressive enhancement: без поддержки пользователь просто видит обычный контент. • Не прятать контент по умолчанию Плохой паттерн: .card { opacity: 0; } А потом раскрывать только внутри scroll-animation. В браузере без поддержки блок останется невидимым. Лучше базово показывать контент, а стартовое состояние задавать внутри @supports. • Анимировать дешёвые свойства transform и opacity — ок. height, top, margin, layout-зависимые штуки — осторожно. Scroll-driven не делает дорогую анимацию бесплатной. • IntersectionObserver всё ещё нужен не всегда, но и не умер Для визуальных reveal/progress эффектов CSS часто проще и чище. Но если нужно грузить данные, отправлять аналитику, управлять состоянием приложения или синхронизироваться с бизнес-логикой — это всё ещё зона JS. • Для sticky-reveal лучше таймлайн секции, а не самого sticky-элемента Sticky-элемент может визуально «залипать», поэтому удобнее повесить view-timeline-name на родительскую секцию, а анимировать внутренний sticky-блок через named timeline. Итог: для progress bars и декоративных sticky-reveal эффектов Scroll-Driven Animations позволяют убрать лишний JS, не плодить observers/listeners и оставить поведение декларативным. Главное — progressive enhancement, prefers-reduced-motion и нормальный fallback.
370
17
Web Locks API в браузере: cross-tab mutex для refresh token, миграций и защиты от гонок Когда одна сессия открыта в нескольки
Web Locks API в браузере: cross-tab mutex для refresh token, миграций и защиты от гонок Когда одна сессия открыта в нескольких вкладках, frontend, SPA, SSR-клиенты и SDK начинают делить auth state, storage и кэш. Частая ошибка - защищать refresh token только in-memory single-flight: между вкладками он не работает. Refresh token как критическая секция Если 5 вкладок одновременно получили 401, они могут отправить несколько refresh-запросов одним token, получить invalid_grant и перетереть свежие токены старыми. Web Locks API дает mutex на один origin: type Tokens = { accessToken: string; refreshToken: string; }; const LOCK = 'auth:refresh-token'; async function ensureAccessToken(): Promise<string> { return navigator.locks.request(LOCK, async () => { const latest: Tokens = await loadTokens(); if (!isExpiring(latest)) { return latest.accessToken; } const next = await refreshTokens(latest.refreshToken); await saveTokens(next); return next.accessToken; }); } Важный паттерн После входа в lock нужно перечитать storage. Пока вкладка ждала, другая уже могла обновить токены. Практический совет: внутри критической секции всегда делайте double-check precondition, а не запускайте refresh сразу после ожидания. Где еще полезно Web Locks хорошо ложится на миграции IndexedDB/localStorage, одноразовую инициализацию кэша, защиту записи в общий browser storage и координацию фоновых задач между вкладками. Ограничения Lock работает только в пределах origin. Это не distributed lock и не замена серверной идемпотентности. Критическая секция должна быть короткой; для сетевого refresh ставьте timeout. Если navigator.locks нет, нужен fallback через BroadcastChannel, storage lease или серверную защиту. Вывод: Web Locks API полезен там, где несколько вкладок делят runtime-состояние: он снижает риск гонок, но не отменяет серверные гарантии.
453
18
День сурка frontend-разработчика Зарплата стоит, скучные задачи день за днем, календарь забит созвонами, которые не влияют во
День сурка frontend-разработчика Зарплата стоит, скучные задачи день за днем, календарь забит созвонами, которые не влияют вообще ни на что. Откликаешься на вакансии, а в ответ тишина либо какие-то мутные конторы. На собесах вместо нормальной оценки навыков цирк с алгоритмами на скорость, как будто ты на олимпиаде, а не работу ищешь. И самое неприятное, пока ты варишься в этом болоте, кто-то спокойно проходит собесы и уходит в Яндекс, VK или на хорошую Валютную удаленку без лишней драмы. Есть классные проекты и сильные команды, где разработчиков действительно ценят, дают расти, поддерживают развитие и платят достойно и ты можешь туда попасть! 👋 Меня зовут Тихон, привет! Я — действующий Frontend-разработчик и ментор. Я за руку довожу до оффера на хорошую позицию в Big Tech и сопровождаю на испытательном сроке. Также из учеников я собираю комьюнити, где уже более 220 frontend-разработчиков🫂 А в своем канале: 👉Объясняю, как проходить HR-фильтр и превращать отклики в реальные приглашения 👉Помогаю найти мотивацию, борюсь убеждениями, которые мешают развиваться 👉На примерах объясняю, как проходить собеседования, включая техничку 👉Разбираю резюме и делюсь лайфхаками, например как аккуратно “пинговать” рекрутеров А еще регулярно публикую полезные материалы: ▪️Задачи, на которых валяться кандидаты ▪️База по микрофронтам ▪️Подборка из 100+ каналов с вакансиями для разработчиков ▪️100 вопросов, которые точно помогут тебе на собеседовании ▪️Чек лист проверки своего резюме А еще у меня множество успешных кейсов и отзывов, найти их можно в канале. Реклама, erid: 2W5zFJbYbow ИП Галактионов Тихон Витальевич, ИНН 771618975809
325
19
Cascade Layers в дизайн-системе: reset, vendors и utilities без войн специфичности В дизайн-системах для SaaS, e-commerce и к
Cascade Layers в дизайн-системе: reset, vendors и utilities без войн специфичности В дизайн-системах для SaaS, e-commerce и кабинетов reset, CSS из npm-пакетов, компоненты и utilities быстро начинают конкурировать. Частая ошибка - лечить это через !important, .foo.foo и порядок импортов. Контракт слоев Сначала зафиксируйте каскад явно: @layer reset, vendors, tokens, base, components, utilities, overrides; Чем слой правее - тем выше приоритет при одинаковом origin/importance. Специфичность работает внутри слоя, но между слоями сначала побеждает порядок @layer. Production-пример @import url("./reset.css") layer(reset); @import url("./datepicker.css") layer(vendors); @layer components { .button { background: var(--accent); } } @layer utilities { .bg-danger { background: var(--danger); } } Теперь .bg-danger предсказуемо переопределит фон компонента, даже если селектор компонента специфичнее. Не нужно делать .bg-danger.bg-danger или надеяться, что файл utilities подключится последним. Практичная схема * reset - нормализация браузерных стилей. * vendors - datepicker, select, editor, carousel. * tokens - CSS custom properties и темы. * base - типографика и дефолты тегов. * components - button, input, modal, tabs. * utilities - атомарные override-классы. * overrides - интеграционные фиксы, которые должны быть видимыми и редкими. Важные нюансы Стили вне @layer сильнее обычных layered-стилей. Поэтому договоритесь: весь CSS дизайн-системы живет в слоях, а приложение переопределяет ее через свой верхний слой. @layer app-overrides; @layer app-overrides { .checkout .button { min-width: 240px; } } Предупреждение: у !important порядок слоев инвертируется. Не строите архитектуру вокруг него - layers нужны, чтобы снижать потребность в таких патчах. Вывод: @layer превращает каскад из побочного эффекта сборки в явный API дизайн-системы, повышая предсказуемость layout и снижая визуальные регрессии.
435
20
⚡️ content-visibility: auto + contain-intrinsic-size: ускоряем длинные страницы без виртуализации и CLS Если на странице мног
⚡️ content-visibility: auto + contain-intrinsic-size: ускоряем длинные страницы без виртуализации и CLS Если на странице много тяжёлых блоков — карточки, статьи, отзывы, секции лендинга, документация — не всегда нужна виртуализация. Иногда достаточно сказать браузеру: «не рендери то, что далеко за экраном». Для этого есть: .content-section { content-visibility: auto; contain-intrinsic-block-size: auto 480px; } Что происходит: - content-visibility: auto разрешает браузеру пропускать рендеринг offscreen-блоков; - layout/paint/style откладываются до момента, когда блок приблизится к viewport; - DOM остаётся на месте: это не виртуализация, элементы не удаляются; - contain-intrinsic-block-size задаёт резервный размер, пока реальная высота ещё не посчитана. Главный нюанс — CLS. Если просто повесить: .card { content-visibility: auto; } браузер может не знать, сколько места должен занимать пропущенный блок. Когда пользователь доскроллит до него, блок отрендерится, получит реальную высоту и может сдвинуть всё ниже. Поэтому вместе с content-visibility почти всегда нужен intrinsic size: .article-preview { content-visibility: auto; contain-intrinsic-block-size: auto 360px; } Здесь 360px — fallback-оценка высоты до первого рендера. Ключевое слово auto позволяет браузеру запомнить реальный размер после рендера и дальше использовать уже его. Практический пример: <main class="feed"> <article class="feed-card">...</article> <article class="feed-card feed-card--large">...</article> <article class="feed-card">...</article> </main> .feed-card { content-visibility: auto; contain-intrinsic-block-size: auto 320px; } .feed-card--large { contain-intrinsic-block-size: auto 560px; } Лучше не подбирать один размер для всего. Если блоки сильно отличаются, разбивайте их на классы: обычная карточка, большая карточка, промо-блок, секция с галереей и т.д. Где это особенно полезно: - длинные ленты без интерактивной виртуализации; - страницы документации; - каталоги с тяжёлыми карточками; - лендинги с большим количеством секций; - SSR-страницы, где DOM уже есть, но рендерить всё сразу дорого. Где быть осторожнее: - above-the-fold блоки — им это обычно не нужно; - sticky/anchor-зависимые интерфейсы; - элементы, размер которых критично влияет на соседей; - списки на десятки тысяч DOM-узлов — тут виртуализация всё ещё может быть нужна; - сложные случаи с измерением размеров через JS. Важно: content-visibility: auto не уменьшает количество DOM-элементов и не отменяет стоимость JS, обработчиков событий или хранения данных в памяти. Он оптимизирует именно рендеринг: layout, paint и часть работы вокруг них. Хороший паттерн: @supports (content-visibility: auto) { .long-page-section { content-visibility: auto; contain-intrinsic-block-size: auto 600px; } } Так это становится progressive enhancement: браузеры с поддержкой получают ускорение, остальные рендерят страницу как обычно. Итог: для длинных страниц, где не хочется усложнять архитектуру виртуализацией, content-visibility: auto — дешёвый способ снизить initial rendering cost. Но без contain-intrinsic-size легко получить layout shifts, поэтому эти свойства почти всегда стоит использовать парой.
453