[Перевод] А есть ли случайные числа в CSS?

2teauihp2yxlixt6ktyuu_5dayw.jpeg

CSS позволяет создавать динамические макеты и интерфейсы в Интернете, но как язык разметки он является статическим — после установки значения его нельзя изменить. Идея случайности не обсуждается. Генерация случайных чисел во время выполнения — это территория JavaScript, а не CSS.

Или нет? Если мы учтем небольшое взаимодействие с пользователем, мы на самом деле можем генерировать некоторую степень случайности в CSS. Давайте взглянем!

Случайность в других языках


Есть способы получить некоторую «динамическую рандомизацию» с помощью переменных CSS, как объясняет Робин Рендл (Robin Rendle) в статье о хитростях CSS. Но эти решения не 100% CSS, так как они требуют JavaScript для обновления переменной CSS новым случайным значением.

Мы можем использовать препроцессоры, такие как Sass или Less, чтобы генерировать случайные значения, но как только код CSS компилируется и экспортируется, значения фиксируются и случайность теряется.

Почему меня волнуют случайные значения в CSS?


В прошлом я разрабатывал простые приложения только для CSS, такие как Викторина, игра CSСимона и Магический трюк, но я хотел сделать что-то более сложное.

*Я оставлю обсуждение обоснованности, полезности или практичности создания этих фрагментов только на CSS на более позднее время.

Исходя из того, что некоторые настольные игры могут быть представлены как конечные автоматы (FSM), значит они могут быть представлены с использованием HTML и CSS.

Поэтому я начал разрабатывать игру «Лила»(она же «Змеи и Лестницы»).

Это простая игра. Если вкратце, то цель игры состоит в том, чтобы продвинуть фишку от начала до конца доски, избегая змей и пытаясь подняться по лестнице.

Проект казался осуществимым, но я кое-что упустил, ага точно — игральные кости!
Бросок костей (в других играх бросок монеты) — общепризнанные методы получения случайного значения. Вы бросаете кости или подбрасываете монету, и каждый раз получаете неизвестное значение. Кажется все просто.

Имитация случайного броска костей


Я собирался наложить слои с метками и использовать CSS-анимацию, чтобы «вращать» и обмениваться тем, какой слой был сверху. Что-то вроде этого:

dmaiize50vmhwclcdluatsww_uy.gif

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

/* Самый высокий z-index  - количество сторон в кости */ 
@keyframes changeOrder {
  from { z-index: 6; } 
  to { z-index: 1; } 
} 
/* Все метки перекрываются с помощью абсолютного позиционирования  */ 
label { 
  animation: changeOrder 3s infinite linear;
  background: #ddd;
  cursor: pointer;
  display: block;
  left: 1rem;
  padding: 1rem;
  position: absolute;
  top: 1rem; 
  user-select: none;
}    
/* Отрицательная задержка, поэтому все части анимации находятся в движении */ 
label:nth-of-type(1) { animation-delay: -0.0s; } 
label:nth-of-type(2) { animation-delay: -0.5s; } 
label:nth-of-type(3) { animation-delay: -1.0s; } 
label:nth-of-type(4) { animation-delay: -1.5s; } 
label:nth-of-type(5) { animation-delay: -2.0s; } 
label:nth-of-type(6) { animation-delay: -2.5s; }


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








You got a:

@keyframes changeOrder {
  from { z-index: 6;}
  to { z-index: 1; }
}

label {
  animation: changeOrder 3s infinite linear;
  background: #ddd;
  cursor: pointer;
  display: block;
  left: 1rem;
  padding: 1rem;
  position: absolute;
  top: 1rem;
  user-select: none;
}

label:nth-of-type(1) { animation-delay: 0s; }
label:nth-of-type(2) { animation-delay: -0.5s; }
label:nth-of-type(3) { animation-delay: -1.0s; }
label:nth-of-type(4) { animation-delay: -1.5s; }
label:nth-of-type(5) { animation-delay: -2.0s; }
label:nth-of-type(6) { animation-delay: -2.5s; }

div {
  left: 1rem;
  position: absolute;
  top: 5rem;
  width: 100%;
}

#d1:checked ~ p span::before { content: "1"; }
#d2:checked ~ p span::before { content: "2"; }
#d3:checked ~ p span::before { content: "3"; }
#d4:checked ~ p span::before { content: "4"; }
#d5:checked ~ p span::before { content: "5"; }
#d6:checked ~ p span::before { content: "6"; }


Но затем я наткнулся на некое ограничение. Я получал случайные числа, но иногда, даже когда я нажимал на «кости», поле не возвращало никакого значения.

Я попытался увеличить время анимации, и это, казалось, немного помогло, но у меня все ещё были некоторые неожиданные значения.

Именно тогда я сделал то, что делают большинство разработчиков, когда они находят препятствия, которые они не могут решить — Я поперся за помощью на StackOverflow. Рекомендую.

К счастью для меня, всегда найдутся люди готовые помочь, в моем случае это был Темани Афиф (Temani Afif) с его объяснением.

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

Из-за подменяющейся анимации верхний слой-метка (label), при нажатии мыши, по сути, не был тем верхним слоем (label -ом) при отжатой мыши, если только я не сделал это достаточно быстро, ну или медленно, чтобы анимация уже успела пройти все значения цикла. Вот почему увеличение времени анимации скрыло эту проблему.

Решение состояло в том, чтобы применить положение «static», чтобы разорвать стековый контекст, и использовать псевдоэлемент, такой как: before или :: after с более высоким z-index, чтобы занять его место. Таким образом, активная метка всегда будет сверху при наведении мыши.

/* Активный тег label будет статичным и перемещен из окна*/ 
label:active {
  margin-left: 200%;
  position: static;
}
/* Псевдоэлемент метки занимает все пространство с более высоким z-index */
label:active::before {
  content: "";
  position: absolute;
  top: 0;
  right: 0;
  left: 0;
  bottom: 0;
  z-index: 10;
}


Вот код с решением с более быстрым временем анимации:
После внесения этого изменения осталось создать небольшой интерфейс для рисования псевдо кубиков, по которым можно щелкнуть мышью, и CSS игра Змеи и Лестницы были завершены.

Эта техника имеет некоторые очевидные неудобства


  1. Требуется взаимодействие с пользователем — необходимо щелкнуть метку, чтобы вызвать «генерацию случайных чисел».
  2. Метод плохо масштабируется — он отлично работает с небольшими наборами значений, но это боль для больших диапазонов.
  3. Это не настоящий Random, а все таки псевдо случайность, и компьютер может легко определить, какое значение будет генерироваться в каждый момент.


Но с другой стороны, это 100% CSS (нет необходимости в препроцессорах или других внешних помощниках), и для человека это может выглядеть на 100% случайно.

И, да этот метод можно использовать не только для случайных чисел, но и для случайного всего. В этом случае мы использовали его, чтобы «случайно» выбора компьютера в игре «Камень-ножницы-бумага».

© Habrahabr.ru