Як приручити випадковість у CSS: практичний розбір random ()

У матеріалі розбирається підхід Brecht De Ruyte до «керованого хаосу» в інтерфейсах за допомогою нативної CSS-функції random(). Автор демонструє, як додати дизайну трохи органічної «недосконалості», не руйнуючи сітки, токени та дизайн-систему.

1. Що робить random () і чому це взагалі цікаво

Ідея: веб давно рухається в бік передбачуваності — чіткі сітки, стабільні відступи, строга типографіка. Функція random() дозволяє повернути в інтерфейси легку «аналогову» хаотичність: дрібні нахили, мікрозсуви, варіації тіней і анімацій.

random() описана в CSS Values and Units Level 5 і генерує псевдовипадкове значення прямо в CSS, без JavaScript чи препроцесорних циклів.

Формально сигнатура виглядає так:

random( <random-caching-key>?, <min>, <max> [, by <step>]? )

На практиці сьогодні в браузерах використовують спрощений варіант із комами:

/* Random angle between -3deg and 3deg */
rotate: random(-3deg, 3deg);

/* Random width between 20px and 80px in steps of 10px */
inline-size: random(20px, 80px, 10px);
/* Random animation duration */
animation-duration: random(1.5s, 3s, 0.2s);
  • Другий параметр — мінімум, третій — максимум.
  • Необов’язковий крок (step) «нарізає» діапазон на дискретні значення.

1.1. Обов’язкова умова: однакові одиниці виміру

Діапазон має бути в одних і тих самих одиницях. Можна працювати з довжинами, кутами, часом, відсотками чи безрозмірними числами, але не змішувати їх.

/* Good: both are angles */
rotate: random(-4deg, 4deg);

/* Good: both are unitless numbers */
opacity: random(0.3, 1);
/* Nope! You cannot mix units */
inline-size: random(2rem, 100px); /* Invalid */

Якщо потрібно комбінувати різні одиниці (наприклад, rem і px), це вже завдання для calc() всередині аргументів.

1.2. Крок (step): як зробити випадковість «прив’язаною до сітки»

Третій аргумент задає крок. Без нього значення береться з безперервного діапазону. З ним — з обмеженого набору дискретних значень:

/* Only produces 0px, 15px, 30px, 45px, or 60px */
top: random(0px, 60px, 15px);

Це зручно, коли:

  • хочеться «стрибків» елементів;
  • але при цьому вони мають опинятися на заздалегідь визначених позиціях (чіткі кратні значенням сітки).

1.3. Кешувальні ключі: однакова випадковість для кількох властивостей

За замовчуванням кожен виклик random() рахується незалежно. Якщо одна картка задає inline-size і block-size через random(), отримаємо різні значення й прямокутник замість квадрата.

Щоб пов’язати кілька властивостей одним випадковим значенням, уводиться кешувальний ключ (умовно схожий за синтаксисом на CSS-змінну):

Приклад коду в оригіналі

Обидві властивості посилаються на один і той самий ключ --square-size, тому браузер використовує одне спільне випадкове значення для ширини й висоти — в результаті блоки лишаються квадратами.

Той самий підхід використовується в прикладі з тайлами:

Приклад коду в оригіналі

  • .tile-uncached — ширина й висота обчислюються як два різні значення, утворюючи випадкові прямокутники.
  • .tile-cached — за рахунок ключа --square-size блоки лишаються квадратами, а окремий ключ --tilt забезпечує стабільний кут нахилу для кожного.

2. Підтримка в браузерах і безпечне впровадження

За описом автора:

  • Safari (WebKit) — першим отримав підтримку random().
  • Chrome / Edge — функція включена, починаючи з Chrome 156, і розкочується.
  • Firefox — є «позитивні сигнали» щодо впровадження, але підтримки на момент написання матеріалу ще немає.

Щоб уникнути поломок у браузерах без підтримки, автор радить використовувати @supports:

Приклад коду в оригіналі

  • Спочатку задається «безпечна» базова стилізація.
  • Потім у @supports — декоративне або анімоване покращення для тих, хто вже вміє random().

2.1. random-item (): випадковий вибір із набору значень

У специфікації також описана функція random-item(). Вона не працює з числами, а дозволяє обрати одне з наперед заданих значень (наприклад, кольорів):

Приклад коду в оригіналі

Це зручно для роботи з палетами та токенами (рандомний вибір з обмеженого списку). У статті підкреслюється, що на момент написання браузерної підтримки в неї ще немає, але автор очікує її появи в майбутньому.

3. Приклади використання: від літер до карток і «боке»

3.1. Демонстрація 1: «криві» літери як декоративний ефект

Автор починає з типографіки: окремі літери в слові відображаються як inline-block із невеликим випадковим нахилом, мікрозсувами по осях та варіаціями прозорості — ефект старого друку чи обкладинки книжки.

Приклад коду в оригіналі

  • rotate і translate додають невеликої хаотичності розташуванню кожної літери.
  • opacity: random(0.8, 1, 0.02) імітує «нерівномірний» накат фарби.
  • На :hover слово «вирівнюється» — всі властивості скидаються до стабільних значень.

Доступність: як не зламати екранні читачі

Щоб стилізувати кожну літеру окремим <span>, текст потрібно спеціально адаптувати для скрінрідерів. Інакше вони проговорюватимуть кожен символ окремо.

Автор радить:

  • виносити декоративні літери в обгортку з aria-hidden="true";
  • на заголовку чи контейнері вказувати aria-label з цілим словом.

Приклад коду в оригіналі

У результаті користувачі з допоміжними технологіями чують повноцінне слово, а не «розбитий» на символи текст.

3.2. Демонстрація 2: процедурний «стрибок» літер

На наступному кроці автор накладає на літери анімацію стрибка: кожна літера отримує власну висоту стрибка, тривалість, нахил, затримку та ступінь «сплющення» при приземленні.

Приклад коду в оригіналі

Ключові нюанси:

  • Всі параметри стрибка задані як CSS-змінні із значеннями через random() (--jump, --tilt, --dur, --delay, --rebound-squash).
  • Негативна затримка (--delay: random(-3s, 0s)) означає, що анімація наче вже триває; при завантаженні сторінки слово не «чекає старту», а одразу живе власним ритмом.
  • Різні animation-timing-function на ключових кадрах створюють природнішу траєкторію (прискорення, приземлення, відскок).

3.3. Демонстрація 3: нахилені картки й тіні, що реагують на кут

Замість суворих колонок картки можна зробити трохи «живими» — дати кожній невеликий кут повороту й вертикальний зсув, а тінь змусити підлаштовуватись під цей нахил.

Приклад коду в оригіналі

Тут показово кілька моментів:

  • --tilt і --shift-y формуються через random(), тому кожна картка має власний характер.
  • У box-shadow використовується calc(var(--tilt) * 1.5) та abs(var(--tilt) * 1px) — тінь «підлаштовується» під напрям і величину нахилу.
  • На :hover і при фокусі всередині (:has(.button:focus-visible)) картка вирівнюється, підіймається й отримує стабільну, більш виражену тінь.

З точки зору UX це важливо: у «спокійному» стані дизайн може бути розхлябаним і грайливим, але при взаємодії все має ставати перпендикулярним і передбачуваним.

3.4. Демонстрація 4: віяло з карток на скролі — комбінація математики й випадковості

У цьому прикладі автор поєднує:

  • математично вивірену структуру через sibling-index() і sibling-count();
  • невеликі випадкові відхилення для «людяності» розкладання.

Приклад коду в оригіналі

Ключові деталі:

  • sibling-index() використовується для обчислення базового кута (--base-rot) і базового зсуву по осі X (--base-x), рівномірно розкидаючи картки навколо центральної незалежно від їх кількості.
  • Змінні --jitter-rot, --jitter-x, --jitter-y додають легке «тремтіння» в межах контрольованих діапазонів.
  • animation-timeline: scroll(root) і animation-range прив’язують розкриття віяла й зміну тіні до позиції скролу — при прокручуванні вгору/вниз колода плавно розкладається й складається.
  • Висота тіней масштабується через --elevation на основі індексу сусіда: картки «вище» у стопці мають помітніший «повітряний» відрив.

Цей кейс добре демонструє: структура (розкладка) має бути детермінованою, а випадковість додається зверху як декоративний шум.

3.5. Демонстрація 5: випадкова швидкість дрейфу текстових рядків

Для модних горизонтальних «маркі» зазвичай або пишуть кілька класів зі статичними швидкостями, або додають JavaScript. Автор показує, як отримати різні швидкості та напрямки тільки на CSS.

Приклад коду в оригіналі

Як це працює:

  • --direction: calc(pow(-1, sibling-index())) дає послідовність 1, -1, 1, -1… — тобто рядки чергують напрям руху ліворуч/праворуч.
  • --travel: random(15%, 44%) задає діапазон відстані, яку проходить рядок — фактично «швидкість» чи «амплітуду» дрейфу.
  • animation-timeline: scroll(root) і animation-range: entry 0% cover 100% знову прив’язують ефект до скролу сторінки.

3.6. Демонстрація 6: «боке» через blur, opacity і scale

Нарешті, автор показує, як відтворити візуальне відчуття малої глибини різкості з фотографії: частина елементів «у фокусі», інші — розмиті та менш контрастні.

Приклад коду в оригіналі

Логіка така:

  • filter: blur(random(0px, 8px, 1px)) задає випадкову ступінь розмиття з кроком 1px.
  • opacity і scale також випадкові в контрольованих межах: далекі об’єкти одночасно дрібніші, тьмяніші й більш розмиті.
  • На :hover чи :focus елемент «витягується в фокус»: blur=0, opacity=1, scale трохи більший.

Такий підхід підходить для декоративних хедерів, тег-клаудів чи «словесних композицій», де потрібен живий візуальний шум без жорсткого семантичного навантаження.

4. Де випадковість доречна, а де — ні

4.1. Сценарії, де random () допомагає

Автор виділяє кілька груп застосувань, де «легка недосконалість» працює на користь:

  • Декоративні елементи інтерфейсу:
    • псевдо-полароїди, стікери, «розкидані» фотографії;
    • легко нахилені бейджі, хедлайни з мінімально «кривими» літерами.
  • Десинхронізація повторюваних анімацій: індикатори статусу, фонові бульбашки, сітки з «плаваючими» точками, де ідеальна синхронність виглядає роботизовано.
  • Генеративні фони та текстури: конфетті, зоряні поля, «noise»-шар для фону, кола «боке» для хедерів.
  • Редакційні та промо-сторінки:
    • лайфстайл портфоліо;
    • сайти подій, концерти, зін-культура, бруталізм;
    • кампанійні лендинги, де обігрується «аналоговість» та «DIY»-естетика.
  • Поверх структурної математики: сітка задається формулами (наприклад, через sibling-index()), а random() додає дрібні відхилення для відчуття «ручної роботи».

4.2. Зони ризику: де random () краще не чіпати

Водночас автор застерігає від безконтрольної рандомізації всього підряд:

  • Основні сітки й макет:
    • ширини колонок;
    • відстані між полями форми;
    • ключові відступи контейнерів.
    Користувачі спираються на стабільну геометрію для орієнтації.
  • Основний текст:
    • font-size, line-height, letter-spacing для абзаців;
    • будь-які речі, що впливають на ритм читання.
    Випадкові відхилення тут швидко приводять до втоми й дискомфорту.
  • Клікабельні цілі:
    • дрібні кнопки;
    • тексти посилань;
    • інтерактивні контролли.
    Якщо кнопка в «стані спокою» ще може бути легенько нахиленою, то на :hover/:focus вона має стояти рівно й передбачувано.

Матеріал не претендує на універсальність: це приклади й позиція конкретного автора, а не «новий закон дизайну». Однак логіка обмеження випадковості тільки декоративними зонами виглядає обґрунтованою.

4.3. Обов’язково враховуйте prefers-reduced-motion

Будь-яка анімація — навіть дуже «легка» — має поважати уподобання користувача щодо зменшення руху. Автор демонструє базовий патерн:

Приклад коду в оригіналі

Корисно не тільки відключати анімацію, але й спрощувати саму композицію (менше кутів, менше «скакань»), щоб скролінг не викликав дискомфорту.

5. Як мислити random () у дизайн-системі

Основна цінність підходу, який описує автор, у тому, що random() розглядається не як «хаос заради хаосу», а як шар контрольованої варіативності, накладений поверх уже стабільної дизайн-системи:

  • Токени, шкали, сітки й типографіка лишаються базою.
  • random() використовується в обмежених діапазонах, із кроком, у «безпечних» зонах (декор, фони, мікро-рух).
  • Кешувальні ключі дозволяють тримати пов’язані властивості в синхроні, уникаючи візуальних «поломок».
  • @supports та prefers-reduced-motion забезпечують прогресивне покращення без шкоди для доступності.

Підсумковий меседж автора: десятиліття дисципліни, токенів і грідів не потрібно «викидати», щойно з’явилась random(). Навпаки — саме завдяки цій основі можна дозволити собі трохи контрольованої, грайливої недосконалості там, де вона доречна.


Джерело: Brecht De Ruyte — Smashing Magazine

Пошук

Закрити