Senior Frontend - javascript, html, css
23.7K subscribers
1.3K photos
2.25K videos
693 links
Senior Front - канал для frontend программистов, практические задачки, проверки знаний, интересные статьи

Админ, сотрудничество, реклама:@seniorFrontPromo, @maria_seniorfront
Канал в реестре РКН https://rknn.link/bsU
Download Telegram
⚽ Одна обёртка, две стороны сетки: турнирная таблица на subgrid

Турнирную сетку плей-офф рисуют симметрично: часть пар раунда слева, часть справа. На сайте FIFA каждая такая половина свёрстана отдельным div, и разметка просто повторяет картинку.

Есть вариант чище. Раунд остаётся одним элементом с детьми .start и .end. Он растягивается на всю общую сетку .rounds и через subgrid получает её колонки и строки:

.round-of-8 {
grid-column: 1 / -1;
grid-row: 1 / -1;
display: grid;
grid-template-columns: subgrid;
grid-template-rows: subgrid;
}


Теперь .start встаёт в первую колонку, а .end в последнюю, хотя в DOM они соседи.

Подвох в выравнивании: align-items: center не ставит матчи соседних раундов друг напротив друга. Помогает flex: 1 у каждого матча внутри flex-колонок .start и .end, чтобы матчи делили высоту поровну.

А в статье видно, как та же разметка на узком экране перестраивается так, что каждый раунд занимает одну колонку.

👉 @seniorFront
👍1
🎉 iframe, который сам подстраивается под содержимое

Обычно у <iframe> фиксированный размер. Встроенная форма или виджет комментариев выросли, и появляется полоса прокрутки.

В Chrome 154 фрейм умеет брать размер из встроенного документа. Нужно согласие обеих сторон. Внешняя страница задаёт фрейму свойство frame-sizing:

/* In styles.css for index.html */
iframe {
frame-sizing: content-height;
width: 100%;
}


А документ внутри фрейма кладёт в head мета-тег responsive-embedded-sizing. Его параметр allow-origins ограничивает, каким сайтам можно сообщать размер.

Размер передаётся после загрузки. Если содержимое изменилось, документ вызывает window.requestResize().

Пока это есть только в Chrome 154, в Firefox и Safari поддержки нет.

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

👉 @seniorFront
❤3🔥1
✨ Одна и та же CSS-переменная может дать 1, а может 2

Кастомные свойства вроде --index по умолчанию хранятся как кусок текста. Вычисляется он там, где переменную подставили через var(), а не там, где объявили.

Пример Джейка Арчибальда. У второго div задано --index: sibling-index() (номер элемента среди соседей, с единицы). Его первый ребёнок, абзац, получает scale: var(--index). Масштаб будет 1: функция считается уже для абзаца.

Теперь зарегистрируем свойство с типом:
@property --index {    syntax: &apos;<number>&apos;;    inherits: true;    initial-value: 0;}

Значение вычислится прямо на div и станет числом 2, абзац увеличится вдвое. С em, lh и единицами контейнерных запросов то же правило.

Но var() внутри значения подставляется сразу при любом типе. Если в --index есть var(--multiplier), переопределять --multiplier у ребёнка бесполезно.

А что при этом происходит с URL из разных таблиц стилей, разобрано в статье.

👉 @seniorFront
📜 Страница скроллится, а window.scrollY показывает 0

У каждой страницы есть главный контейнер прокрутки, root scroller. Только он получает восстановление позиции при возврате «назад», прокрутку пробелом и стрелками, сворачивание адресной строки на телефоне.

Потерять его легко. Вот частая комбинация:

html,
body {
height: 100%;
overflow-x: hidden;
}


Из-за overflow-x: hidden по вертикали у body получается auto. А height: 100% делает body ровно в высоту экрана. Теперь прокручивается body, а не страница. Внешне всё как раньше, ошибок нет.

Проверить просто: прокрутите вниз и выполните в консоли window.scrollY. Ноль значит, что скроллится что-то другое.

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

В статье разобрано, как сделать липкую шапку, футер внизу и блокировку прокрутки под модалкой, не теряя root scroller.

👉 @seniorFront
❤1
📐 Как заменить CSS progress(), пока его поддерживают не везде

Функция progress() показывает, где значение лежит между двумя границами, и возвращает число от 0 до 1. Формула простая: (значение - начало) / (конец - начало).

Но поделить 180px на 880px и получить просто число умеют не все браузеры: Firefox, например, пока нет. Выручает tan(atan2()). Функция atan2() по сути сама делит первый аргумент на второй, а tan() отдаёт обычное число без единиц.

--progress: tan(atan2(500px - 320px, 1200px - 320px));


Так мы узнаём, где 500px между 320px и 1200px. Чтобы результат не выходил за пределы от 0 до 1, как у progress(), оберните его в clamp().

С разными единицами, например vw и px, трюк кроссбраузерно не сработает. Зарегистрируйте ширину экрана через @property как <length>, и браузер сам переведёт её в пиксели.

Как быть, если границы заданы в rem, и какой вариант выбрать под ваши единицы, автор разбирает в статье.

👉 @seniorFront
❤1
🔑 Обработчик postMessage без проверок слушается кого угодно

Виджет в iframe (комментарии, форма оплаты) не может сам растянуться на странице. Поэтому он шлёт родителю сообщение через postMessage, а родитель в обработчике message выставляет iframe новую высоту.

Подвох в том, что браузер доставит сообщение от любого окна, у которого есть ссылка на ваше. Чужая страница одной строкой пришлёт { type: &apos;resize&apos;, height: 1 }, и виджет схлопнется до одного пикселя.

Спасает строгое сравнение event.origin с ожидаемым адресом. Это поле заполняет сам браузер, и отправитель не может его подделать.

if (event.origin !== WIDGET_ORIGIN) return;


Но origin говорит, с какого сайта пришло сообщение, а не из какого окна. Два iframe с одного домена пройдут проверку оба, и сообщение от одного изменит размер другого.

Как это закрыть через event.source и что ещё проверить в самих данных (вроде height: 1e9), автор показывает на живых демо с тремя атаками.

👉 @seniorFront
🔍 Почему overflow: hidden не удерживает размытие

Свойство backdrop-filter: blur() размывает всё, что видно сквозь полупрозрачный элемент. Так делают эффект матового стекла. Снимок фона браузер берёт из готовой GPU-текстуры (compositing layer), а не из DOM. Кто чей родитель, ему неважно.

В статье есть живой пример. Карточки с blur(70px) лежат в scroll-контейнере с overflow: hidden, а снаружи стоит яркая кнопка PLAY. Карточка подъезжает к кнопке ближе чем на 70px и начинает светиться её цветом.

Дело в том, что overflow: hidden обрезает на этапе paint, а размытие работает позже, на этапе composite. Помогает clip-path: inset(0) на контейнере: автор называет его единственным надёжным способом.

Только помните, что clip-path обрежет всё содержимое контейнера, включая дочерние элементы, которые выходят за его край.

В статье ещё три бага в Safari и на iPhone и разбор, почему большой радиус blur так дорого обходится GPU.

👉 @seniorFront
❤1
🔗 Линия между двумя блоками без JavaScript

Линия между двумя карточками сама тянется за ними, когда они двигаются. Скрипт для расчёта координат не нужен.

Это CSS Anchor Positioning: элемент с anchor-name становится якорем, а блок с position: absolute привязывает к нему свои стороны через anchor(), даже при разных родителях.

Главный трюк: привязать противоположные стороны блока к двум разным якорям, и он растянется между ними:

.anchor--one { anchor-name: --a1; }
.anchor--two { anchor-name: --a2; }

.route--one {
position: absolute;
left: anchor(--a1 right);
right: anchor(--a2 left);
top: anchor(--a1 center);
bottom: anchor(--a2 center);
}


В фон кладём SVG с диагональю и background-size: 100% 100%. В самом SVG нужен preserveAspectRatio="none", иначе линия не растянется по обеим осям.

👉 @seniorFront
🚀 Два атрибута у картинки, и страница перестаёт прыгать

Пока файл картинки грузится, тег img занимает на странице ноль места. Потом картинка приходит, блок раздвигается, и текст под ним резко уезжает вниз. Браузер тратит процессорное время на пересчёт раскладки, а читатель теряет строку, которую читал.

Лечится это заранее заданными пропорциями. У тега указываем width=800 height=600, а в CSS пишем width: 100% и height: auto. Картинка растянется на ширину контейнера, а высоту браузер вычислит из пропорции 800×600 ещё до загрузки файла. Место зарезервировано, ничего не дёргается.

В демо-замере из статьи это сэкономило около 12% процессорного времени.

Не забудьте про height: auto: без него картинки растягиваются неправильно.

В статье ещё разобраны старые трюки с padding-bottom и aspect-ratio и объяснено, почему они проигрывают простым атрибутам.

👉 @seniorFront
👍2🔥1
🪄 Один URL: из списка открывается modal, по прямой ссылке целая страница

Если modal с деталями хранится только в useState, адрес не меняется. Ссылкой не поделиться, после reload снова список.

В Next.js App Router можно иначе. Страница events/[id] работает сама по себе. А рядом в layout живёт слот @modal (в URL он не попадает) с перехватчиком того же адреса:

@modal/
(.)events/
[id]/
page.tsx


Префикс (.) значит «перехватить events/[id] на том же уровне». Переход по Link без перезагрузки перехватывается, и детали рисуются поверх списка. Прямая ссылка или reload перехват не включают: открывается полная страница с metadata и canonical.

Подвох: слоту нужен default.tsx, возвращающий null. Без него при прямом открытии @modal остаётся без маршрута, и можно получить 404.

В статье разобрано, как после закрытия вернуть пользователя на ту же страницу поиска со всеми фильтрами.

👉 @seniorFront
👍1
🔴В 2025 году Россияне заработали 5,3 млрд рублей на арбитраже криптовалют

По данным Минфин 32% граждан покинули своё место работы поскольку уже в первый месяц смогли заработать 250 000 рублей, а через 2-3 месяца с легкостью вышли на семизначные числа.

Самое смешное, что лишь 8% россиян что-то слышали об этой профессии хотя все инструкции бесплатно лежат на канале Арбитраж Криптовалют

Там объясняют, как имея телефон и доступ в интернет заработать свои первые $3000. Причём разжёвывают так доступно, что поймёт даже ребенок. Кстати, на канале сегодня вышла новая рабочая связка!

Ссылка для входа — https://t.me/+GY-3aS-FN5c0Yjhi
Please open Telegram to view this post
VIEW IN TELEGRAM
👍2👎2❤1🔥1
АЙТИШНИКИ БЕСПЛАТНОЕ ОБУЧЕНИЕ

Проект «Terminal» стал крупнейшей библиотекой бесплатного образования. В одном канале собраны курсы, книги, полезные инструменты и практические тренажёры для всех разработчиков:

• Практические курсы и задания
• Книги и статьи известных авторов
• Полезные инструменты и ресурсы
• IT-новости и инсайды

Обучение по всем направлениям: SQL, Python, ML, Frontend, PHP, C++, Go, Git, Linux, QA, Java, Vibe-coding, InfoSec и др.

⌨️ подписаться
Please open Telegram to view this post
VIEW IN TELEGRAM
❤1
✨ Подсветка текущего раздела в оглавлении без JavaScript

На длинной странице оглавление может подсвечивать раздел, который вы сейчас читаете. Этот приём зовут scrollspy, и обычно он держится на JavaScript, например на IntersectionObserver.

Новое CSS-свойство scroll-target-group делает это без скриптов. Ставим его на обёртку с якорными ссылками:

.toc {
scroll-target-group: auto;
}


Теперь браузер сам связывает каждую ссылку с её разделом. Ссылка на раздел, который сейчас на экране, получает псевдокласс :target-current, и её остаётся только покрасить. Для пунктов выше и ниже есть :target-before и :target-after.

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

В статье к этому добавляют линию, которая плавно доезжает до текущего пункта через anchor positioning.

👉 @seniorFront
👍1
🔍 Chrome ругается «Blocked aria-hidden»? Не глушите его

Обычно это всплывает при закрытии модалки. Окно уже помечено aria-hidden="true", а фокус ещё на кнопке «Закрыть» внутри. Атрибут прячет элемент от скринридера, но не от Tab, и фокус застревает там, чего для скринридера «нет».

Популярный фикс blur() чистит консоль, но роняет фокус на <body>. Пользователь скринридера слышит тишину и листает Tab с самого начала страницы.

Чинится это порядком действий. Сначала фокус возвращается на кнопку, которая открыла окно, и только потом окно прячется. На время анимации закрытия окну ставят inert: оно выпадает из Tab, и скринридер его не видит.

Подвох в том, что inert блокирует и фокус. Если фон за окном был inert, снимите его до focus(), иначе фокус молча не переедет.

В статье разобраны четыре сценария этой ошибки и готовый код для чистого JS и React.

👉 @seniorFront
❤1
🤖Podlodka Backend Crew: AI в бэкенд-задачах

Демо с AI чаще всего выглядят одинаково: агент за десять минут пишет сервис с нуля.

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

🗓 С 26 по 30 октября в сезоне Podlodka Backend Crew «AI в бэкенд-задачах» участники:

• Научатся передавать агенту инженерные стандарты и правила, чтобы он писал так, как нужно, а не как получится

• Соберут ревью, где несколько моделей независимо проверяют изменения, а финальное решение остаётся за человеком

• Посмотрят, куда переезжает сложность миграции легаси, когда код пишет агент

• Выяснят, где ломается мечта отдать миграцию агенту целиком

• Разберут, какие антипаттерны тестов агенты
уже успели перенять от разработчиков.

Формат — пять дней живых Zoom-сессий утром и вечером, закрытое сообщество в Telegram и общение со спикерами.

Будет много интерактива и практики. Базовый ликбез оставят за бортом — этот сезон для тех, кто уже пробует AI в реальных проектах. Присоединяйтесь с 26 октября!

🔗 Билеты и программа: https://podlodka.io/becrew
Please open Telegram to view this post
VIEW IN TELEGRAM
Please open Telegram to view this post
VIEW IN TELEGRAM
🔥1
🧩 Компонент рисует, хук думает

В React-компоненте разметка часто перемешана с подготовкой данных: проверками, вычислениями, обработкой ошибок. JSX от этого читается тяжело.

Статья на Хабре предлагает идею, подсмотренную во Vue 3: вынести подготовку в хук с именем useSetup[Component]. Для панели профиля ProfilePanel это useSetupProfilePanel. Он отдаёт флаг isLoading и готовые пропсы для карточки, а в компоненте остаётся только разметка.

Тесты от этого тоже упрощаются. В тесте компонента хук подменяется моком (vi.mock из Vitest), и проверяется одно: попали ли пропсы из хука в нужный элемент. Логику самого хука проверяют его собственные тесты.

Важно понимать, что shallow-тест через Shallowly (библиотеку автора статьи) не делает полный рендер. Дочерний компонент не выполняется, поэтому клики и изменения DOM проверяет отдельный DOM-тест.

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

👍 - если было полезно

👉 @seniorFront