Создаем падающий снег на HTML и CSS

Image by Rachel Burkum from Pixabay

В последнее время в Техасе холодно и снежно. Это вдохновило меня на создание анимации с падающим снегом. Делается это быстро, все вместе занимает от силы 10 минут.

Примечание. Я буду использовать Pug и Sass/SCSS для упрощения повторяющихся частей HTML и CSS соответственно. Но это не обязательно. Вы можете экстраполировать код и сделать все на чистых HTML и CSS.

Вот как наша анимация будет выглядеть:

See the Pen Snowfall animation by Alvaro Montoro (@alvaromontoro) on CodePen.

Создаем фон

Давайте начнем с фона. Этот шаг опционален, его можно сделать по-разному. Для целей этой статьи мы ограничимся просто темным фоном со следующим CSS:

html, body {
  padding: 0;
  margin: 0;
  width: 100vw;
  height: 100vh;
  position: relative;
  overflow: hidden;
  background: linear-gradient(#123, #111);
}

Добавляем снежинки

Затем мы создаем блок <div> для каждой снежинки, которая будет у нас на экране. Мы можем сделать что-то типа такого:

<div class="snowflake"></div>
...
...
...
<div class="snowflake"></div>
<div class="snowflake"></div> <!-- 50 раз! -->

Но для простоты лучше применим PugJS. Он позволяет использовать циклы для повторяющихся задач:

- for (i = 0; i < 50; i++)
  div(class="snowflake")

Стилизация снежинок

Теперь у нас есть блоки <div> для всех снежинок на странице. Осталось их стилизовать. Снежинки будут маленькими, круглыми и белыми:

.snowflake {
  --size: 1vw;
  width: var(--size);
  height: var(--size);
  background: white;
  border-radius: 50%;
  position: absolute;
  top: -5vh;
}

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

Также мы позиционировали снежинки вне области видимости (сверху). Падать они будут в зону, также находящуюся вне области видимости (снизу).

Анимация

Чтобы анимировать снегопад, мы используем CSS-анимацию с @keyframes. Начнем с чего-то простого, а затем немного усложним.

Для начала мы применим translate3d, чтобы заставить снежинки двигаться вертикально. Поскольку это 3D-преобразование, оно будет триггерить ускорение железа и выглядеть красивее, чем если бы мы анимировали другое свойство, например top:

@keyframes snowfall {
  0% {
    transform: translate3d(0, 0, 0);
  }
  100% {
    transform: translate3d(0, 110vh, 0);
  }
}

Мы могли бы применить эту анимацию для класса snowflake, добавив следующее свойство:

animation: snowfall 5s linear infinite;

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

Мы могли бы создать 50 разных правил, по одному для каждой снежинки, назначив для каждой разные позиции left, углы и скорости. Но, хотя сделать это на чистом CSS вполне реально, это все же куча работы:

.snowflake:nth-child(1) {
  --size: 0.6vw;
  left: 55vw;
  animation: snowfall 8s linear infinite;
}

...

.snowflake:nth-child(49) {
  --size: 1vw;
  left: 78vw;
  animation: snowfall 7s linear infinite;
}

.snowflake:nth-child(50) {
  --size: 1.5vw;
  left: 20vw;
  animation: snowfall 10s linear infinite;
}

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

@for $i from 1 through 50 {
  .snowflake:nth-child(#{$i}) {
    --size: #{random(5) * 0.2}vw; /* randomize size! */
    left: #{random(100)}vw;
    animation: snowfall #{5 + random(10)}s linear infinite;
  }
}

Бац! Всего 7 строк кода, которые позже скомпилируются в 250! И нам не нужно придумывать рандомные числа, поскольку в SCSS для этого есть специальная функция random().

Последние штрихи

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

/* uses CSS variables to determine initial and final position */
@keyframes snowfall {
  0% {
    transform: translate3d(var(--left-ini), 0, 0);
  }
  100% {
    transform: translate3d(var(--left-end), 110vh, 0);
  }
}

@for $i from 1 through 50 {
  .snowflake:nth-child(#{$i}) {
    --size: #{random(5) * 0.2}vw;
    --left-ini: #{random(20) - 10}vw; /* random initial translation */
    --left-end: #{random(20) - 10}vw; /* random final translation */
    left: #{random(100)}vw;
    animation: snowfall #{5 + random(10)}s linear infinite;
    animation-delay: -#{random(10)}s;
  }
}

Наконец, мы добавляем отрицательную задержку animation-delay, чтобы не все снежинки стартовали одновременно. В противном случае все они начали бы свое падение в один момент, что выглядело бы странно.

Апдейт: переместим переменные в HTML

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

Идея в том, чтобы перенести объявление CSS-переменных из CSS в HTML:

<!-- 1 -->
<div class="snowflake" style="--left: 69vw; --left-ini: -4vw; --left-end: 0vw; --speed: 8s; --size: 0.4vw; --delay: -10s;"></div>
...
...
...
<!-- 50 -->
<div class="snowflake" style="--left: 83vw; --left-ini: 5vw; --left-end: 1vw; --speed: 10s; --size: 0

Нам нужно определить функцию random в PugJS, а затем использовать ее для установки значений CSS-переменных:

- function random(num) { return Math.floor(Math.random() * num) }
- for (i = 0; i < 50; i++)
  div(class="snowflake", style=`--left: ${random(100)}vw; --left-ini: ${random(20) - 10}vw; --left-end: ${random(20) - 10}vw; --speed: ${5 + random(15)}s; --size: ${random(5) * 0.2}vw; --delay: -${random(15)}s;`)

Теперь, когда все эти значения есть в HTML, мы можем удалить все CSS-селекторы и поместить свойства анимации прямо в определении .snowflake:

.snowflake {
  width: var(--size);
  height: var(--size);
  background: white;
  border-radius: 50%;
  position: absolute;
  top: -5vh;
  left: var(--left);
  animation: snowfall var(--speed) linear infinite;
  animation-delay: var(--delay);
}

Это решение делает CSS-код существенно меньше и проще (из 500 строк остаются 34!), хотя и привносит немного сложности в HTML.

Итоги

Падающий снег — простая, но привлекающая внимание анимация. Вместе с тем она может быть довольно ресурсоемкой, если снежинок будет очень много. Будьте осторожны.

Видео создания анимации можно посмотреть на YouTube:

Перевод статьи «Creating a snowfall effect with HTML and CSS».

[customscript]techrocks_custom_after_post_html[/customscript]

[customscript]techrocks_custom_script[/customscript]

Прокрутить вверх