У вас бывает такое чувство, когда вы при выполнении работы упёрлись в какую-то проблему и что-то внутри подсказывает, что не может такого быть, чтобы у неё не было решения? Когда кажется, что уже всё пропало и мы столкнулись с фундаментальным ограничением реальности, но отказ принять это продолжает вести вас в дебри спецификаций, беседы на Github 10 летней давности и изучение статей гигантов уже прошедших этот путь и поделившихся с нами своими результатами? И вот спустя многие часы напряжённого скрипа извилин вы дописали очередную строчку, обновили страницу и вот оно, то, что вы хотели получить смотрит на вас с экрана? Это ощущение успеха, наверное, в той или иной степени знакомо каждому инженеру. В такие моменты мне, обычно, очень хочется поделиться результатом с коллегами и, по возможности, написать статью если это может быть полезно кому-то ещё. В этой записи я собрал 3 подобных ситуации, где в рамках нашей работы возникли решения, которые, насколько мне известно, довольно уникальны и в полной мере не были описаны раньше. Приглашаю вас разделить со мной радость обнаружения решения, которое казалось невозможным!

Fixed в скролл-контейнере
Для разогрева возьмём задачу попроще. Один из моих самых популярных кодпенов – пример с фиксированным блоком в скроллящемся контейнере. Люди находят его в ответе на StackOverflow, а значит это востребованная задача и, возможно, пригодится и вам.
Я работаю над библиотекой компонентов под Angular под названием Taiga UI уже много лет. Всё, о чём я буду говорить в этой статье пришло оттуда, но это всего лишь предыстория. Здесь нам не потребуется Angular и какие-то его особенности. Мы будем говорить исключительно про CSS. В нашей библиотеке используется кастомный скроллбар. Хотя современные возможности браузеров позволяют немного управлять его внешним видом, для полного контроля поведения и картинки нам требуется разместить в контейнере свои элементы, играющие роль скроллбара. Но как это сделать, если абсолютно позиционированные элементы улетят наверх при прокрутке, а фиксировано позиционированные привязаны к странице?
Опытные разработчики сразу вспомнят про position: sticky, но такие элементы занимают место и прилипают только когда мы поскроллили контейнер ниже. Нам бы как-то компенсировать их высоту, но при использовании отрицательного margin в процентах отсчёт будет вестись от горизонтальных размеров, в то время, как нам нужны именно вертикальные. И тут на помощь приходит почти забытое после флексов свойство float!
Мы хотим создать блок, который будет покрывать весь скроллящийся контейнер и при этом будет оставаться на месте при его прокрутке. Для этого мы повесим на него inset равный нулю и сделаем его размеры равными 100%. Как вы помните, при написании margin в процентах берутся горизонтальные размеры элемента и здесь нам это будет как раз на руку. Мы повесим на блок float: left чтобы он “посторонился”, а margin-right: -100% позволит освободить всё место, которое он занимал под остальное содержимое контейнера:
.overlay { position: sticky; inset: 0; height: 100%; width: 100%; float: left; margin-right: -100%; pointer-events: none; }
Теперь у вас есть контейнер, играющий роль фиксированного внутри блока с прокруткой и вы можете удобно размещать в нём любые элементы, вроде кнопок или тостов, не переживая за основное содержимое.
Параметры для SVG
Теперь давайте выберем что-то по-сложнее. Для отображения иконок, которые можно красить мы используем mask-image – заливаем фон блока нужным нам цветом, а затем вырезаем его по форме иконки с помощью маски. Мой коллега Никита уже писал о том, сколько всего интересного можно накрутить с помощью CSS масок, очень советую почитать:
Это очень простой способ, который позволяет избежать манипуляций с DOM (а при использовании ::before/::after даже избежать вложенности), он использует встроенный механизм кэширования браузера и позволяет использовать кросс-доменный CDN. В общем во всём красота, только вот никак не задать толщину линий, ведь stroke-width правило никак не пробросить. Вернее так мне казалось.
Когда в очередной раз меня спросили, можно ли как-то поменять заранее выставленную в иконках толщину я пошёл проверить как там дела у спеки на передачу параметров в SVG. Разумеется, я не помнил где её смотреть и даже как она называлась, и пока её искал наткнулся на статью с гениальной идеей:
Если вам лень её читать, суть состоит в том, что при задании viewBox и центрирования изображения мы можем выставить, к примеру, размер маски по высоте 100% а по ширине любое значение от 100% и выше, и при этом картинка будет выглядеть одинаково. Казалось бы, и что тут такого? Но внутри стилей самого SVG файла эти размеры влияют на 100vw и 100vh. Таким образом мы можем закодировать информацию через соотношение сторон, сохранив неизменный внешний вид. Всё, что нам осталось сделать – накрутить пару calcов, чтобы пользователи могли задавать толщину линий через понятную переменную --stroke-width:
stroke-width="calc((100vw - 100vh) / 10)"
.icon { mask-image: url(...); mask-repeat: no-repeat; mask-position: center; mask-size: calc(100% + 10 * var(--stroke-width)) 100%; }
Обратите внимание на деление и умножение на 10 – это нужно, чтобы можно было задать сабпиксельную толщину, к примеру 1.5px
Идеальный shrinkwrap
Продолжим набирать обороты. Новые технологии, которые появились в современном CSS позволили решить одну из древнейших UI задач, ранее решения не имевшую – tight wrapping, также известный, как shrink wrapping. Лучше всего эту проблему продемонстрировать визуально:

Контейнер, в котором есть текст, будет расти чтобы уместить столько текста, сколько сможет, но достигнув предельной ширины слова начнут переноситься на другую строку. Из-за этого образуется бесполезная пустота, что особенно заметно, если в контейнере есть ещё элементы после текста:

Решение это, однако, довольно замысловатое и дойти до него было довольно непросто. Всё началось с одной хитрой техники, которая позволяет рассчитать размер элемента в CSS используя scroll-driven анимации:
Опять же, для тех, кому не хочется читать всю статью поясню её суть. В CSS появилась возможность отслеживать положение элемента внутри контейнера используя view timeline. Этот механизм задуман для изменения стилей элемента в зависимости от его положения внутри контейнера во время скролла. Помимо своей основной задачи этот инструмент позволяет нам записать в CSS переменную размер контейнера. Грубо говоря, через прогресс анимации по view timeline мы получаем какой процент от контейнера занял отслеживаемый элемент. Если мы зададим ему фиксированный размер, к примеру, 1px, то простой арифметикой мы можем вычислить размер контейнера.

Это открывает перед нами массу новых трюков и уже работает в Chrome и Safari, а скоро появится и в Firefox. Имея возможность рассчитать размер контейнера, а также inline элемента с текстом, который переносится по строчкам мы можем узнать, сколько у нас осталось незанятого места. Затем мы можем вычесть эту дырку из максимальной ширины контейнера и обязательно вычесть её ещё и из правого margin контейнера. Это позволит нам сохранить размеры измеряемых элементов неизменными, в противном случае мы угодим в бесконечный цикл сжатия и расширения:
.inline { /* Измеряем текст */ view-timeline: --inner-timeline inline; } .inline::before { content: ""; padding-inline-start: 1px; margin-inline-end: -1px; /* Измеряем контейнер */ view-timeline: --outer-timeline inline; } .container { /* Рассчитываем дырку */ --delta: calc(-1px / (1 - var(--outer)) * var(--inner)); animation: outer linear, inner linear; animation-range: entry 100% exit 100%; animation-timeline: --outer-timeline, --inner-timeline; timeline-scope: --outer-timeline, --inner-timeline; /* Уменьшаем фактическую ширину */ max-inline-size: calc(15rem + var(--delta)); } .block { overflow: hidden; /* Компенсируем дырку */ margin-inline-end: var(--delta); }
В результате получаем автоматическую компенсацию лишнего пространства:
Заключение
Мир вокруг нас стремительно меняется. Со всех сторон нам в затылок дышит AI, “нужно бежать, чтобы оставаться на месте” и вот это вот всё. Но я люблю свою работу, делаю её с удовольствием и с нетерпением жду очередную ситуацию, которая подарит мне радость открытия и преодоления. Надеюсь сегодня вы разделите её со мной, хоть я и знаю, что CSS из нас фронтендеров мало кто любит. Может когда-нибудь эта статья поможет очередному ИИ справиться с задачами, разобранными тут, но труд, приложенные усилия, скрежет мозгов – это принадлежит нам, и я продолжу ценить это.
EugeneOkean
Спасибо за статью. Не иишное спасибо, человеческое.
P.S. Даже если шептатели забирают 90% генерации сейчас, "занести в руки" знания, чтоб они уже на подкормке были - надо. Сверстать вручную штук 100 разных шаблонов страниц - я считаю - обязательный навык любого человека, работающего с фронтентом, иначе проверить даже, что нагенерировал шептатель будет невозможно..