Фотография
нажмите — покажем
нажмите — покажем
🪄 Как убрать лишний div, не трогая HTML
В CSS сетка или флексбокс действуют только на прямых детей контейнера. Если между ними затесалась обёртка, её содержимое живёт в своём мирке и в общую раскладку не попадает.
Свойство display: contents решает это так: сам элемент как коробка исчезает (фон, рамка, отступы пропадают), а его дети становятся детьми родителя. Похоже на «разгруппировать» в графическом редакторе.
Пример из статьи. В <figure> лежит div с тремя картинками и рядом <figcaption>. Подпись никак не встроить в сетку фотографий, потому что она снаружи div. Пишем
.photo-group {
display: contents;
}
и теперь браузер видит три картинки и подпись как четырёх равноправных детей <figure>, а сетку задаём прямо на нём.
Важная оговорка: с таблицами, заголовками, кнопками и списками это ломает доступность, автор советует там не применять и проверять результат.
В статье ещё семь живых раскладок, где приём спасает.
18 · 4.9K · Фотография
нажмите — покажем
нажмите — покажем
📱 Быстро жмёте кнопку на телефоне, а страница вдруг зумится
На одном веб-радио при быстром переключении станций с мобильного браузер то приближал, то отдалял страницу. Viewport meta при этом был настроен правильно.
Виноват жест двойного тапа. Safari на iOS принимает два быстрых нажатия за просьбу увеличить элемент, в данном случае саму кнопку.
Лечится одной строкой CSS:
.button {
touch-action: manipulation;
}
Прокрутка и масштабирование двумя пальцами продолжают работать, отключается только зум по двойному тапу. По словам MDN, браузеру заодно не нужно задерживать событие click в ожидании второго тапа.
Есть соблазн вместо этого дописать в viewport maximum-scale=1.0, user-scalable=no. Не надо: так зум пропадает целиком, а это нарушение WCAG.
В оригинале два демо на CodePen, с кнопками без этого свойства и с ним: потыкайте их с телефона и почувствуйте разницу.
👉 @seniorFront
12 · 1.5K · Фотография
нажмите — покажем
нажмите — покажем
🎯 Иконка в списке съезжает, когда подпись переносится на вторую строку
Иконка и текст стоят в flex-контейнере с align-items: center. Когда текст переносится, иконка встаёт посередине всего блока, а не напротив первой строки.
С align-items: start она поднимается выше букв: у строки есть пустое место от шрифта. Сдвиг на фиксированные 3px ломается, как только меняется шрифт или размер иконки.
Надёжнее считать сдвиг через CSS-единицу lh, она равна высоте строки:
.icon {
/* --icon-size is defined via JS. */
--size: var(--icon-size);
--offset: calc((1lh - var(--size)) / 2);
transform: translateY(var(--offset));
}
Иконка сдвигается на половину разницы между высотой строки и своим размером и встаёт по центру первой строки при любом шрифте. Подвох в том, что размер иконки нужно знать заранее.
А для иконки в ::before понадобятся ещё aspect-ratio и height: auto, и это отдельная история.
👉 @seniorFront
12 · 1.3K · Фотография
нажмите — покажем
нажмите — покажем
✅ Свой чекбокс, который не ломает фокус
Родной чекбокс в CSS почти не настраивается: по сути, только цвет через accent-color. Псевдоэлемент ::checkmark уже есть в спецификации, но, по словам автора, браузеры над ним пока не работают.
Приём простой: настоящий <input> не прячут, а кладут внутрь span с рамкой, снимают родной вид и растягивают поверх коробки:
.checkbox input {
margin: 0;
width: 100%;
height: 100%;
appearance: none;
position: absolute;
top: 0;
left: 0;
border-radius: 0.2em;
}
Его не видно, но он по-прежнему получает фокус и рисует кольцо фокуса. Галочка — отдельная SVG рядом, появляется по селектору input:checked + svg.
Оговорка: для промежуточного состояния нужна ещё одна SVG, которую переключают.
Тот же код подходит для радиокнопок и масштабируется одним font-size.
👉 @seniorFront
14 · 1.1K · Фотография
нажмите — покажем
нажмите — покажем
📦 Почему карточка ломается на широком мониторе
Медиазапрос @media знает только ширину окна. Положите карточку в колонку 300px на экране 1920px, и правило min-width: 1024px всё равно сработает. Карточка получит широкую раскладку и начнёт сжиматься и вылезать за края.
Контейнерный запрос @container спрашивает другое: сколько места у компонента именно здесь. Обёртку объявляем контейнером, а стили карточки завязываем на её ширину:
.card-wrapper {
container-name: card;
container-type: inline-size;
}
@container card (min-width: 450px) {
.card {
display: flex;
flex-direction: row;
}
}
Теперь карточка встаёт в ряд, только если у обёртки есть 450px по горизонтали. Хоть на телефоне, хоть в узком сайдбаре десктопа.
Подвох: контейнер не может запрашивать сам себя. Чтобы менять .card, её нужно обернуть ещё одним элементом.
В статье ещё типографика на единицах cqi и способ поймать перенос строки во flexbox без JavaScript.
👉 @seniorFront
10 · 938 · Фотография
нажмите — покажем
нажмите — покажем
✨ Как не оставить один пункт меню одного на новой строке
Когда во флексбоксе включён перенос (flex-wrap: wrap), элементы уходят на следующую строку, как только перестают помещаться. Бывает, что там оказывается ровно один: пять пунктов меню сверху и одинокий «Speaking» снизу.
Обычно это чинят медиазапросом, но он легко ломается, стоит поменять текст. Новое значение flex-wrap: balance делает иначе: браузер сам раскладывает элементы по строкам ровнее, без расчётов ширины.
.channels {
display: flex;
flex-wrap: balance;
justify-content: center;
gap: 1rem;
}
Для сравнения: тот же ряд иконок соцсетей без balance приходится выравнивать обходными путями, от контейнерных запросов до display: contents. Все четыре варианта из разбора заметно длиннее.
Оговорка: на момент публикации это работает только в Chrome, начиная со 150-й версии.
Рядом появилось ещё свойство flex-line-count, которое задаёт минимальное число строк, и на нём получается даже экспериментальная masonry-раскладка.
👉 @seniorFront
7 · 802 · Фотография
нажмите — покажем
нажмите — покажем
⚽ Одна обёртка, две стороны сетки: турнирная таблица на 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
8 · 557 · Фотография
нажмите — покажем
нажмите — покажем
🎉 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
13 · 629 · Ссылка
нажмите — покажем
нажмите — покажем
Некоторые фейки специально выглядят немного странно. Создателю важно вызвать сильную эмоцию, а не идеальную реалистичность. Шок, страх или жадность отключают спокойную проверку. Поэтому сильная эмоциональная реакция — повод сделать паузу. Загляните дальше — покажем, как использовать эту паузу себе на пользу.
https://m6p8GEbmjK.linkas.net
Фотография
нажмите — покажем
нажмите — покажем
✨ Одна и та же CSS-переменная может дать 1, а может 2
Кастомные свойства вроде --index по умолчанию хранятся как кусок текста. Вычисляется он там, где переменную подставили через var(), а не там, где объявили.
Пример Джейка Арчибальда. У второго div задано --index: sibling-index() (номер элемента среди соседей, с единицы). Его первый ребёнок, абзац, получает scale: var(--index). Масштаб будет 1: функция считается уже для абзаца.
Теперь зарегистрируем свойство с типом:
@property --index { syntax: '<number>'; inherits: true; initial-value: 0;}
Значение вычислится прямо на div и станет числом 2, абзац увеличится вдвое. С em, lh и единицами контейнерных запросов то же правило.
Но var() внутри значения подставляется сразу при любом типе. Если в --index есть var(--multiplier), переопределять --multiplier у ребёнка бесполезно.
А что при этом происходит с URL из разных таблиц стилей, разобрано в статье.
👉 @seniorFront
6 · 514 ·