В прошлых статьях я рассматривал тему реализации псевдослучайности в CSS при помощи операции целочисленного деления и использовал простые числа для создания автоматического счётчика, при помощи которых можно генерировать разные значения для каждого объекта. Благодаря этому мы можем вычислять псевдослучайные значения для каждого элемента по отдельности.

Несмотря на надёжность этого решения, оно имеет и недостатки:

  • Функция деления с остатком не непрерывна
  • Способ слишком сложен: он требует трёх переменных и определения @property для каждого случайного значения, которое мы хотим сгенерировать
  • Требует применения @property, которое пока поддерживается не очень широко

К счастью, можно сделать лучше! В этой статье я предложу более оптимальное решение на основе тригонометрии.

Более оптимальное решение


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

Случайность и псевдослучайность


Очевидно, что представленное в статье решение генерирует только псевдослучайные значения. Все значения вычисляются при помощи заранее заданных констант. Как говорится в моей предыдущей статье, можно добавить дополнительную переменную --seed и изменять её вне системы (например, задавать её при загрузке JavaScript), чтобы результат был менее детерминированным, но у CSS нет никаких недетерминированных методов. Тем не менее, этого решения должно быть достаточно для получения приемлемых значений для анимаций, позиций и так далее. Если вы хотели использовать его для решения своих криптографических функций, то, возможно, изначально выбрали не ту технологию.

Характеристики функции синуса


Функции синуса и косинуса интересны по множеству причин. Они могут быть очень полезны во всех операциях, где задействуются окружности и вращения. Однако в нашем случае мы можем использовать их свойства для других целей.

Ограниченная функция


Одно из замечательных свойств синуса и косинуса в том, что получающиеся значения всегда ограничены интервалом от -1 до 1. Это означает, что вне зависимости от величины передаваемого им значения результат всегда будет значением из этого интервала. После чего мы можем выполнить простую нормализацию в интервал [0,1]. Нормализовав значения, мы можем использовать их для задания любого другого значения при помощи простого линейного сопоставления.

--x: calc(0.5 + 0.5 * sin(var(--n) * 342.06184 + 23.434));

/* Затем мы можем использовать это следующим образом */
background: rgb(calc(50 + var(--x) * 100), 0, 0);
/* Красный будет находиться в интервале 50-150 */

В показанном выше коде используется мой счётчик var(--n), описанный в моей предыдущей статье, где я использую простые числа для обеспечения эффективного способа автоматического создания переменной счётчика в CSS.

Counting in CSS: Unlock magic of CSS variables

Затем значение умножается и смещается на произвольные значения, чтобы создать псевдослучайное большое число (значения не особо важны, вы можете изменять их, чтобы получать разные результаты). После этого мы используем функцию синуса, чтобы отобразить его в интервал [-1, 1]. Далее, как показано на анимации ниже, мы можем отобразить его в интервал [0, 1], применив простое алгебраическое преобразование. Получив значение из интервала [0, 1], мы можем применить линейное сопоставление, чтобы отобразить его на любое желаемое значение.


Непрерывность


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


Примеры


Вот несколько примеров, демонстрирующих потенциал применения тригонометрических функций при генерации псевдослучайных значений.

Сетка из кругов


Первый пример показывает в действии свойства синуса. Генерируемые значения случайны, однако мы всё равно можем сохранить порядок и ощущение непрерывности анимирования цветов и размеров.


Codepen

Основная часть кода — это вычисление переменных x, y, z и w, описывающих, соответственно, красный, зелёный, синий и ширину.

div::before {
  --x: calc(0.5 + 0.5 * sin(4.284 * var(--n)));
  --y: calc(0.5 + 0.5 * sin(7.284 * var(--n)));
  --z: calc(0.5 + 0.5 * sin(4 * var(--n) + 2 * var(--t)));
  --w: calc(0.5 + 0.5 * sin((0.2 * cos(var(--t)/100) + 0.8) * 49.123 * var(--n) + var(--t)));
  
  background: rgb(
    calc(50 +  100 * var(--x)),
    calc(200 + 30 * var(--y)),
    calc(120 + 100 * var(--z))
  );
  width: calc(50% + var(--w)*50%);
}

Последние две переменные наряду с нашим счётчиком --n используют переменную времени --t, которая получается выполнением анимации, постепенно меняющей переменную:

@property --t {
  syntax: '<number>'; /* <- задаётся как тип number, чтобы этот переход работал */
  initial-value: 0;
  inherits: true;
}
:root {
  --t: 0;
}

@keyframes timeOn {
  50% {--t: 30}
}

html {
  animation: 30s timeOn infinite linear;
}

Это единственная часть кода, где используется @property. Чтобы это работало во всех браузерах, мы можем просто обновлять эту переменную в JavaScript, не теряя возможность вычислять всё остальное в обычном CSS.

Пятна


Также случайность можно использовать с элементами SVG, что в сочетании с фильтрами SVG делает её мощным инструментом. Источником вдохновения для показанного ниже демо стала потрясающая статья с CSS-Tricks под названием The Gooey Effect.


Codepen

Позиция каждого отдельного пятна определяется при помощи простой формулы. Единственное отличие заключается в том, что мы используем для их стилизации cx, cy, r и fill, потому что это элементы SVG.

.blobs > circle {
  --x: calc(sin(var(--t) + var(--n) * 74.543 + 432.43312));
  --y: calc(cos(var(--t) + var(--n) * 2.34 + 1.432));
  --v: calc(0.5 + 0.5 * sin(var(--n) * var(--n) * 4.343 + 2.673));
  
  cx: calc(10vw + var(--x) * 80vw);
  cy: calc(10vh + var(--y) * 80vh);
  r: calc(var(--v) * 5vw + 1vw);
}

Для получения эффекта липкости мы используем следующий фильтр SVG:

<filter id="goo">
    <feGaussianBlur in="SourceGraphic" result="blur" stdDeviation="15" />
    <feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 22 -11" result="goo" />
    <feBlend in2="goo" in="SourceGraphic" result="mix" />
</filter>

Паттерн в стиле Memphis


Последнее демо — это обновлённая версия примера, который я использовал в предыдущей попытке обеспечения случайности в CSS, когда я применил оператор деления с остатком. В моём новом решении вычисления понимать и изменять намного проще.


Memphis Pattern using Trigonometry randomness

Комментарии (4)


  1. maximw
    05.10.2023 13:38
    +1

    Почему не нормализовать через abs?

    Ну и без изучения распределения (оно должно быть равномерным на любом подмножестве аргументов генератора) это никак нельзя назвать псевдослучайными числами. И если ограниченность полезное свойство для генератора псевдослучайности, то непрерывность скорее вредит.


  1. dom1n1k
    05.10.2023 13:38

    Любопытный подход, но это тот случай, когда вульгарная императивщина на JS будет лучше декларативного костыля с любой точки зрения. Гибче, понятнее, предсказуемее, лучше поддерживается браузерами и тд.


    1. Pshir
      05.10.2023 13:38

      del


  1. Pshir
    05.10.2023 13:38

    Плотность вероятности в вашей реализации: https://www.wolframalpha.com/input?i=y%3D2%2F(pi*sqrt(1-(2x-1)^2))