Используем random() правильно
• css
Введение
CSS совсем скоро стукнет 30 лет (смотря откуда считать), и практически к этой дате мы получим CSS-функцию для получения случайных значений random() в трёх браузерных движках:
Однако не всё так просто. Да и вообще, нужен ли этот random, если 30 лет жили без него?
Задача
Сама задача random не нова. Больше 10 лет назад у Романа Комарова (kizu) была статья про бросание кубика на чистом CSS, и, насколько я понимаю, он участвовал в написании спеки про random() в нынешнее время.
Зачем же вообще получать случайные значения? Тут может быть много вариантов:
- Случайная раскраска элементов, которая бы была одинаковой для одинаковых значений. Это могут быть теги у элементов в списке, или заголовок
Server-Timing, цвета для элементов которого хочется видеть одинаковыми при каждой загрузке. - Случайные стили одинаковых элементов. Хороший пример – стопки фотографий, где каждая стопка имеет по-разному повёрнутые фотографии.
- Снежинки и звёздное небо. Постепенно становится холоднее, а совсем скоро сайты начнут наполняться снежинками –
randomотлично подходит для реализации!
Но я бы всё-таки отличал использование random от “неповторяющихся” элементов (например, принцип цикады). Случайные числа стоит использовать, если нужен разный результат, а принцип цикады – когда нужен одинаковый псевдо-случайный. Небольшая разница всё же есть.
Например, если хочется получить неповторяющийся фон, то, на мой взгляд, принцип цикады подходит лучше. Аналогичная история с текущей шапкой MDN. Часть элементов имеют разные цвета, однако за этим есть строгая логика. К тому же, это навигация – пользователи будут теряться, если один и тот же элемент будет выглядеть по-разному при каждом открытии страницы.
random
random(min, max)
random(min, max, step)
random(seed, min, max)
random(seed, min, max, step)min, max, step должны иметь один тип – число, размер или процент. И да, min и max – включительно, кто-то вспомнит математическую запись [min, max]. Базовое использование:
.block {
width: random(50px, 150px);
}Всё, на этом расходимся… Шутка. Самая большая проблема – когда будет новое случайное значение, а когда – старое. К слову, в примере выше элемент не будет получать новую ширину каждую секунду. Ещё один подводный камень – в поддержке. Разные значения seed поддерживаются по-разному.
Каждое случайное значение имеет “базу”. seed влияет на инициализацию этого базового значения и за его переиспользование. Два случайных числа с одинаковым базовым значением и одинаковыми остальными параметрами будут одинаковыми.
Я попробовал разбить на разные случаи использования.
Разные значения в разных элементах и свойствах
Базовое использование, без seed. Оно же значение auto:
/* прямоугольник */
.block {
width: random(50px, 150px);
/* тоже самое */
width: random(auto, 50px, 150px);
height: random(50px, 150px);
}Это может приводить к неожиданным эффектам:
.block {
width: random(50px, 150px);
}
.block:hover {
width: random(50px, 150px);
}Данная конструкция не приведёт к изменению размера при наведении мыши. Дело в том, что значение random() кешируется между вызовами, и при использовании auto значение получается одинаковым для одного и того же свойства одного и того же элемента. А значит, если random() позовут в другом CSS-правиле, но для того эе элемента и того же свойства, то получится такое же значение.
Есть нюанс: если вызовов random() в одном свойстве несколько, то при таком использовании каждый вызов получит своё значение. Это может быть полезно для разных компонентов цвета.
Значение auto соответствует element-scoped property-index-scoped (на них мы посмотрим позже).
Разные значения в каждом элементе, одинаковые в разных свойствах
.square {
width: random(--size element-scoped, 50px, 150px);
height: random(--size element-scoped, 50px, 150px);
}Синтаксис изменился, и в старых статьях можно встретить другие ключевые слова (не буду их приводить, чтобы лишний раз не путать), будьте бдительны.
В данном варианте использования все random внутри одного элемента получат одно и то же значение. --size – произвольный идентификатор, который будет “объединять” все случайные значения. Таким образом, не нужно беспокоиться, если --size встретится для другого компонента, ведь элемент будет другой, а значит, и случайное значение тоже будет другим.
Ещё один нюанс – элемент и его псевдоэлементы считаются разными элементами.
Одинаковые значения в разных элементах, разные для разных свойств
.block {
background: hsl(random(property-scoped, 0deg, 360deg) 100% 50%);
}
.block::before {
background: hsl(random(property-scoped, 0deg, 360deg) 100% 50%);
}Есть два варианта: property-scoped и property-index-scoped. Оба будут получать одинаковые значения для одного свойства у разных элементов. Разница будет при использовании нескольких random() внутри одного свойства. В первом варианте значения всех random() внутри свойства будут иметь одинаковую базу, во втором – разные.
Значение может быть полезно для псевдоэлементов: если нужно получить случайный фон у основного элемента и сделать такой же фон у его декоративного псевдоэлемента (хвостик тултипа), то property-scoped как раз придётся к месту.
Одинаковые значения в разных элементах и свойствах
.block {
background: hsl(random(--accent, 0deg, 360deg) 100% 50%);
}Подобный подход может использоваться для получения одинакового значения, например, выбрать случайную тему для всей страницы (акцентный цвет).
Фиксированные значения
.block {
width: random(fixed 0.5, 50px, 150px);
}fixed полезно для тестирования – оно предоставляет фиксированное значение. Однако, если хочется автоматически получать в тестах фиксированное значение, а в продакшене – разное, то придётся выдумывать свою систему.
Другие варианты
Для seed можно указывать auto, fixed, набор ключевых свойств или идентификатор, начинающийся с --, рядом с которым тоже можно указать набор ключевых свойств.
Ключевые свойства такие:
element-scoped– добавляет элемент к “базе”property-scoped– добавляет имя свойстваproperty-index-scoped– добавляет имя свойства и номер вызоваrandom()внутри него
Некоторые комбинации рассмотрели выше. Я постарался собрать наиболее полезные по вариантам применения.
Остальные варианты тоже возможны, и их поведение будет согласно комбинации. Например, рассмотренный выше auto имеет element-scoped property-index-scoped, т..е случайная величина будет разной в разных элементах и в разных свойства.
Что с CSS-переменными?
Думаю, читатель мог не раз задаться вопросом: как это всё работает с CSS-переменными? Может, и не нужна вся эта сложность с ключевыми словами?
И действительно, на поверхности лежит логика (она НЕПРАВИЛЬНАЯ, так НЕ РАБОТАЕТ): если хочется одинаковое случайное значение, то просто поместим его в CSS-переменную:
.block {
/* НЕ СРАБОТАЕТ */
--size: random(50px, 150px);
width: var(--size);
height: var(--size);
}Думаю, по коду очевидно, что имелось ввиду: заводим переменную, получаем случайный размер, используем его. Что же пошло не так?
Дело в том, что CSS-переменные, не объявленные через @property, работают, как подстановки строк. В данном же случае строка random(...) подставляется два раза, а затем вычисляется по логике random: случайное значение для каждого свойства и элемента. Тем самым это будет прямоугольник, а не квадрат (если только не повезёт).
Что с @property? После объявления свойство становится “полноценным”, и random() начинает вычисляться уже для него:
@property --size {
syntax: "<length> | <percentage>";
inherits: true;
initial-value: 0px;
}
.block {
/* Теперь работает */
--size: random(50px, 150px);
width: var(--size);
height: var(--size);
}Чуть-чуть аккуратнее
Есть два соображения.
Имейте ввиду, что случайные значения могут приводить к плохим комбинациям. Например, можно получить маленький размер шрифта, выползающий за пределы текст или неконтрастное сочетание цветов.
random()плохо тестируется и воспроизводится. Получив одну “хорошую” комбинацию значений, нельзя быть уверенным, что всегда всё будет хорошо.
Итого
Удивительно замудрёное свойство для просто формулируемой задачи. Синтаксис, лежащий на поверхности, скрывает тонну нюансов и имеет неочевидные подводные камни. Конструкция, которая выглядит очевидно и понятно, может работать абсолютно не так.
Боюсь представить, сколько переделок потребовалось сделать в браузерах, чтобы подобная уникальная логика была реализована. Отсюда и дополнительные проблемы с браузерной поддержкой.
В любом случае, random() - впечатляющее достижение современных браузеров. Каких-нибудь 10 лет назад про подобное можно было бы услышать “невозможно”, а теперь мы живём в мире style query, @scope, и многом другом. random() не теряется на их фоне.
Ждём random-item()?




