Есть ли в CSS случайные числа?

Автор оригинала: Alvaro Montoro
  • Перевод
CSS позволяет создавать динамические макеты страниц и интерфейсы веб-проектов. Но CSS — статический язык. После того, как задано некое значение, изменить его нельзя. Идея случайного изменения неких значений здесь не рассматривается.



Генерирование случайных чисел — это территория JavaScript, на которую CSS не заходит. А что если это не совсем так? На самом деле, если принять в расчёт действия, выполняемые пользователем, это позволит добавить в CSS немного случайности. Автор материала, перевод которого мы сегодня публикуем, предлагает это обсудить.

Внешняя рандомизация CSS-значений


Для реализации в CSS чего-то вроде «динамической рандомизации» можно применить CSS-переменные. Вот хороший материал об этом. Однако подобные решения проблемы — это не чистый CSS. Тут требуется прибегнуть к возможностям JavaScript для записи в CSS-переменные новых случайных значений.

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

Почему мне интересен вопрос использования случайных значений в CSS?


Однажды я занимался разработкой простых приложений, основанных исключительно на CSS. Это — викторина, игра Саймон и карточные фокусы. Но мне хотелось сделать что-нибудь посложнее. Я не затрагиваю тут вопросы правильности такого подхода, вопросы полезности или практической применимости проектов, основанных исключительно на CSS.

Основываясь на предпосылке, в соответствии с которой некоторые настольные игры можно представить в виде конечных автоматов (Finite State Machines, FSM), можно прийти к выводу о том, что такие игры можно реализовать, используя только HTML и CSS. Вооружившись этой идеей, я начал разработку игры «Змеи и лестницы». Это — простая игра. Её цель заключается в том, чтобы, бросая игральную кость, прибыть из начального пункта игрового поля в конечный, избегая при этом змей и стремясь воспользоваться лестницами.

Мне казалось, что этот проект вполне можно сделать на HTML и CSS. Однако кое-что я не учёл. Речь идёт об игральной кости.

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

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


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


Имитация анимации слоёв в браузере

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

/* Самое большое значение 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; }

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

Вот проект на CodePen, который позволяет исследовать этот подход


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

Собственно говоря, тут я и столкнулся с проблемой. Случайные значения моя программа выдавала, но иногда, даже когда я щёлкал по кнопке, имитирующей бросок кости, система вообще ничего не возвращала.

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

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

Мне, к моему счастью, всё объяснили, и предложили вариант решения проблемы.

Упрощённое описание моей проблемы можно представить так: «Браузер вызывает событие click элемента лишь тогда, когда элемент, являющийся активным в момент возникновения события mousedown, остаётся активным при возникновении события mouseup».

Так как элементы постоянно сменяют друг друга — верхний элемент, на котором при нажатии кнопки мыши возникает событие mousedown, не всегда является тем же элементом, на котором, при отпускании кнопки, возникает событие mouseup. Для того чтобы нажатие и отпускание кнопки пришлись бы на тот момент, когда в верхней части стопки находится один и тот же элемент, щелчок нужно выполнять или достаточно быстро (так элемент не успеет уйти из верхней части стопки), или достаточно медленно (так у элемента есть шанс вернуться в верхнюю часть, сделав полный круг). Именно поэтому увеличение времени анимаций позволило замаскировать проблему.

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

/* Активный элемент получает статическое позиционирование и выводится за пределы окна */ 
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. Для его использования не нужны препроцессоры или некие внешние вспомогательные механизмы. А для пользователя его использование выглядит так, будто программа выдаёт совершенно случайные числа.

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


Игра «Камень, ножницы, бумага» на чистом CSS

Уважаемые читатели! Планируете ли вы использовать в своих проектах идеи, освещённые в этом материале?


  • +25
  • 5,7k
  • 6
RUVDS.com
1 509,40
RUVDS – хостинг VDS/VPS серверов
Поделиться публикацией

Комментарии 6

    +8
    Что-то похожее недавно было.
    habr.com/ru/post/474818
      +4
      RUVDS не читатель, RUVDS писатель.
      0
      нужно добавить больше элелементов label с другими таймингами.
      И сделать блокировку на момент нажатия.
      label:active {
      z-index:7 !important;
      background-color: pink;
      animation:none;
      }
        0

        А где можно поиграть в эти игрушки?

          0

          По ссылкам "вот" и "этот".

          0

          Как раз, числа не "псевдослучайные", потому что участвует человек и определить заранее когда он нажмет на кнопку невозможно в принципе.

          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

          Самое читаемое