Дайджесты новостей
Плакатная графика веб-интерфейса, демонстрирующая нативную функцию CSS random() с параметрами диапазона и ключом кэширования per-element.

Случайности в чистом CSS: как устроена функция random() и что она изменит в верстке

Сколько раз в своей практике ты ловил себя на мысли: «Чёрт возьми, почему в CSS до сих пор нельзя просто сгенерировать случайное число?»

Каждый раз, когда дизайнеры просят оживить сайт органическими эффектами — разбросать снежинки на праздничном лендинге, слегка повернуть стикеры с отзывами на случайный угол в 2–3 градуса, добавить мерцание звёздам на фоне или разнести задержки появления карточек в каталоге, — фронтендер упирается в глухую стену.

Исторически у нас было ровно два костыля:

  1. Препроцессоры (Sass / SCSS): в них есть функция random(), но она вычисляется ровно один раз во время сборки проекта на сервере. В итоговом CSS ты получаешь жестко захардкоженные числа, которые одинаковы для всех посетителей и требуют раздувания стилей сотнями громоздких селекторов :nth-child(n).
  2. JavaScript-костыли: вычислять Math.random() в коде компонента и пробрасывать значения в инлайн-стили style="--delay: ...". Это загрязняет разметку, нагружает память виртуального DOM и ломает декларативную красоту стилей.

Ситуация наконец-то меняется: рабочая группа CSSWG включила нативную функцию random() в официальную рабочую версию спецификации CSS Values and Units Module Level 5.

Давай подробно разберём, как устроена эта функция, какие грабли поджидают верстальщиков и почему её появление в браузерах — это долгожданный праздник для веб-анимации.

Синтаксис и грамматика: единицы измерения, диапазоны и шаг

В отличие от стандартного Math.random() в JavaScript, который возвращает безразмерное число с плавающей точкой от 0 до 1, CSS-функция random() изначально спроектирована под физические единицы веба: пиксели, проценты, градусы, секунды и относительные шрифтовые единицы.

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

/* Базовый случайный угол от -5 до 5 градусов */
.sticky-note {
  transform: rotate(random(-5deg, 5deg));
}

/* Случайный размер шрифта с дискретным шагом 4px */
.tag-cloud-item {
  font-size: random(12px, 28px, 4px);
}

/* Случайная задержка анимации с шагом в одну десятую секунды */
.animated-card {
  animation-delay: random(0s, 1.5s, 0.1s);
}

Обрати внимание на третий аргумент — шаг (step). Если ты указываешь random(10px, 50px, 10px), браузер выберет строго одно из дискретных значений: 10px, 20px, 30px, 40px или 50px. Это позволяет легко привязывать случайные отступы и размеры к модульной сетке дизайн-системы, исключая появление дробных размытых пикселей на экранах стандартной плотности.

Главный архитектурный вызов: проблема мерцания и ключ детерминизма

Казалось бы, всё просто? Но когда инженеры браузерных движков начали продумывать интеграцию случайности в рендеринг, они столкнулись с фундаментальной проблемой: жизненным циклом перерисовки (Repaint / Reflow).

Представь: ты задал случайный угол поворота кнопке. Пользователь наводит курсор мыши (:hover), страница скроллится или изменяется размер окна. Браузер запускает перерасчёт стилей. Если бы random() каждый раз генерировал новое число, кнопка при каждом движении мыши дёргалась бы в припадке эпилепсии! А при перерисовке таблицы всё оформление непрерывно скакало бы, вызывая катастрофический сдвиг макета (Cumulative Layout Shift).

Чтобы решить эту проблему, в спецификацию добавили гениальное концептуальное понятие: ключ кэширования (caching key / seed identifier), о котором детально рассказывает разработчик Google Chrome Брэм Ван Дамм в разборе в блоге Bramus.

Синтаксис с явным ключом выглядит так:

/* Значение генерируется один раз и стабильно привязано к конкретному элементу */
.gallery-photo {
  transform: rotate(random(--photo-seed, -4deg, 4deg));
}

Как это работает под капотом:

  • Первый аргумент с двойным дефисом (--photo-seed) служит идентификатором генератора.
  • Браузер вычисляет псевдослучайное значение один раз для конкретного элемента в дереве DOM и кэширует его.
  • Никакие ховеры, скроллы или изменения родительских классов больше не сбивают это значение: элемент остаётся визуально стабильным на протяжении всей жизни страницы.

Более того, если тебе нужно синхронизировать несколько свойств одного элемента (например, чтобы ширина и высота случайного пузыря были строго пропорциональны), ты используешь один и тот же ключ:

.particle {
  --size: random(--bubble-seed, 10px, 60px);
  width: var(--size);
  height: var(--size);
  /* Но задержка должна быть уникальной, поэтому ключ другой! */
  animation-delay: random(--time-seed, 0s, 3s);
}

Родственная функция random-item() для работы со списками значений

Помимо числового диапазона, спецификация вводит парную функцию random-item(). Она позволяет выбирать случайное значение не из непрерывного отрезка, а из произвольного перечня ключевых слов, цветов или идентификаторов:

/* Случайный выбор акцентного цвета из палитры дизайн-системы */
.badge {
  background-color: random-item(--badge-color, var(--color-blue), var(--color-emerald), var(--color-purple));
}

Это полностью избавляет от необходимости писать километровые каскады селекторов :nth-child(4n + 1) только для того, чтобы раскрасить теги в блоге в разные фирменные цвета.

Где random() идеален, а где категорически противопоказан

Функция random() открывает второе дыхание для чистого CSS, но требует профессионального чувства меры:

Идеальные сценарии применения:

  • Органические фоновые паттерны (звёздное небо, дождь, частицы пыли).
  • Рассинхронизация зацикленных анимаций (чтобы 20 плавающих элементов не двигались строем, как солдаты).
  • Лёгкие рукописные микро-отклонения (имитация напечатанных на машинке букв, естественные наклоны карточек).

Опасные сценарии, которых стоит избегать:

  • Размеры колонок в сетках Grid и Flexbox: случайная ширина блоков сломает предсказуемость адаптивной верстки и вызовет неконтролируемые переносы строк.
  • Цвета критического текста: случайные оттенки неизбежно приведут к нарушению контрастности WCAG на светлом или тёмном фоне.

Если ты следишь за новыми стандартами веб-платформы, обязательно изучи наш материал про новинки Chrome 156: случайные числа в CSS, и scroll-snap. Нативный CSS стремительно вытесняет необходимость в JavaScript там, где речь заходит о визуальной выразительности интерфейса.