[Перевод] Есть ли в CSS случайные числа?
CSS позволяет создавать динамические макеты страниц и интерфейсы веб-проектов. Но CSS — статический язык. После того, как задано некое значение, изменить его нельзя. Идея случайного изменения неких значений здесь не рассматривается.
А что если это не совсем так? Генерирование случайных чисел — это территория JavaScript, на которую CSS не заходит. Автор материала, перевод которого мы сегодня публикуем, предлагает это обсудить.
На самом деле, если принять в расчёт действия, выполняемые пользователем, это позволит добавить в CSS немного случайности.
Внешняя рандомизация CSS-значений
Для реализации в CSS чего-то вроде «динамической рандомизации» можно применить CSS-переменные. Вот хороший материал об этом. Однако подобные решения проблемы — это не чистый CSS. Тут требуется прибегнуть к возможностям JavaScript для записи в CSS-переменные новых случайных значений.
Но после компиляции и экспорта CSS-кода эти значения оказываются фиксированными и элемент случайности теряется. Для генерирования случайных значений можно воспользоваться препроцессорами вроде Sass или Less. В одном твите на эту тему такой подход к установке CSS-значений сравнивается со случайным выбором имени героя романа, которое, будучи однажды написанным на бумаге, уже не изменяется.
Почему мне интересен вопрос использования случайных значений в CSS?
Однажды я занимался разработкой простых приложений, основанных исключительно на CSS. Это — викторина, игра Саймон и карточные фокусы. Но мне хотелось сделать что-нибудь посложнее. Я не затрагиваю тут вопросы правильности такого подхода, вопросы полезности или практической применимости проектов, основанных исключительно на CSS.
Вооружившись этой идеей, я начал разработку игры «Змеи и лестницы». Основываясь на предпосылке, в соответствии с которой некоторые настольные игры можно представить в виде конечных автоматов (Finite State Machines, FSM), можно прийти к выводу о том, что такие игры можно реализовать, используя только HTML и CSS. Её цель заключается в том, чтобы, бросая игральную кость, прибыть из начального пункта игрового поля в конечный, избегая при этом змей и стремясь воспользоваться лестницами. Это — простая игра.
Однако кое-что я не учёл. Мне казалось, что этот проект вполне можно сделать на HTML и CSS. Речь идёт об игральной кости.
Каждый раз, бросая кость или монету, мы получаем нечто такое, что было нам до этого неизвестно. Бросок кости (а также бросок монеты) пользуются всеобщим признанием в качестве «генераторов» случайных значений.
Имитация броска игральной кости
Я собирался наложить друг на друга слои с метками и использовать CSS-анимацию для того, чтобы их «прокручивать», меняя верхний слой. Выглядело это примерно так, как показано ниже.
Имитация анимации слоёв в браузере
Он включает в себя описание анимации с использованием различных задержек. Код, реализующий подобную систему получения случайных значений, не особенно сложен. Вот этот код:
/* Самое большое значение z-index — это количество сторон игральной кости. */ @keyframes changeOrder 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
. Именно поэтому увеличение времени анимаций позволило замаскировать проблему.
Далее, его место занимал псевдо-элемент, вроде ::before
или ::after
, которому было назначено очень большое значение z-index
. Решением проблемы было применение значения static
для свойства position
активного элемента, что изымало его из стопки элементов. При таком подходе активный элемент всегда находился бы в верхней части стопки при отпускании кнопки мыши.
/* Активный элемент получает статическое позиционирование и выводится за пределы окна */ 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
Уважаемые читатели! Планируете ли вы использовать в своих проектах идеи, освещённые в этом материале?