Звездопад на CSS

Как-то стало интересно можно ли сделать анимированный звездопад используя CSS (если быть точнее — Sass). Оказалось, что можно, причем в зависимости от настроек результат также может быть похож на заставку Windows “Сквозь вселенную”.

Что получилось:

See the Pen Stars or snow? by yoksel (@yoksel) on CodePen.

Для перезапуска кликните Rerun в правом нижнем углу или откройте полную версию.

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

Исходный HTML выглядел как-то так:

<div class="stars stars--1"></div>
<div class="stars stars--2"></div>
<div class="stars stars--3"></div>
<div class="stars stars--4"></div>
<div class="stars stars--5"></div>
<div class="stars stars--6"></div>
<div class="stars stars--7"></div>
<div class="stars stars--8"></div>
<div class="stars stars--9"></div>
<div class="stars stars--10"></div>
<div class="stars stars--11"></div>
<div class="stars stars--12"></div>
<div class="stars stars--13"></div>
<div class="stars stars--14"></div>
<div class="stars stars--15"></div>
<div class="stars stars--16"></div>
<div class="stars stars--17"></div>
<div class="stars stars--18"></div>
<div class="stars stars--19"></div>
<div class="stars stars--20"></div>

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

Задаем исходные параметры:

/* Начальный цвет точек, на основе него
   будут получены другие оттенки, более светлые */
$color: blue;
/* Переменная с прозрачным цветом для более короткой записи */
$transp: transparent;

/* Длительность анимации */
$time: 25s;
/* Максимальное количество слоев, соответствует количеству дивов,
   но можно задать меньшее число */
$max: 20;
/* Шаг для задержки анимации */
$step: $time/$max;

/* Размеры отдельных градиентов для одного элемента */
$sizes: 5em, 7em, 9em, 11em;

/* Пустая переменная, в которую можно записывать отладочную
   информацию, чтобы вывести её потом в свойство content */
$debug: null;

Идея с отладочной переменной пришла мне в голову когда потребовалось разобраться со странностями в градиентах. Наверное, есть путь проще, но я писала код в codepen.io, а там @debug ничего никуда не выводит.

Переменная заполняется вот таким образом:

$debug: append($debug, $your-variable, comma);

$your-variable добавляется в конец списка, элементы которого разделяются запятой (это задается последним параметром comma).

Затем значение $debug можно вывести внутри комментария:

 /*

#{$debug}

*/

Очень удобно. UPD: То есть, конечно очень неудобно лазить за отладочной информацией в исходник фрейма и делать такое длинное присваивание, но совсем без дебага было бы вообще невозможно сделать сколь-нибудь сложные конструкции.

CSS (а точнее SASS):

HTML,
BODY {
  height: 100%;
}

BODY {
  background-color: black;
  /* Размеры в em, поэтому размер шрифта лучше задать явно */
  font-size: 10px;
}

/* Слой со звездами, растянутый на весь размер окна */
.stars {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  animation: bg $time linear infinite;
  }

/* Миксин, генерирующий стили для слоев
   и задающий для них расположение градиентов,
   длительность и задержку анимации */
@include printStars;

@keyframes bg{
  100% {
    /* Функция с размерами при наличии параметра
       увеличивает все размеры на заданный коэффициент */
    background-size: getSizes(20);
    /* Вращение небосвода. Если отключить,
       получится заставка "Сквозь вселенную" :) */
    transform: rotateZ(180deg);
  }
}

/* Вывод отладочной информации */
/*

#{$debug}

*/

Функции в Sass выглядят примерно вот так:

@function function_name( $somevar ){
  $result: null;

  /* do something */

  @return $result;
}

В функцию можно передавать параметры, также можно сразу задать в скобках дефолтное значение, например $somevar: 20em.

Чтобы вернуть результат вычислений, нужно использовать @return.

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

Миксин с классами для слоев:

@mixin printStars(){

  @for $i from 2 through $max {

    .stars--#{$i} {
     /* Градиенты слоя рисуются функцией */
     background: gradients();
     /* Размеры слоя также генерятся функцией
        на основе заданных параметров */
     background-size: getSizes();
     background-position: center center;
     animation-delay: -$step*$i;
     animation-duration: $time — $i*.7;
   }

  }
}

Функция, рисующая звезды (радиальные градиенты):


@function gradients(){
  $output: null;
  /* Количество градиентов определяется длиной массива с размерами ($sizes) */
  @for $i from 1 through length($sizes) {
    /* Цвет получается из исходного с помощью Sass-функции lighten().
       Степень осветления зависит от текущего шага.
       Это позволяет не задавать список используемых цветов,
       а получать их автоматически  */
    $current-color: lighten($color, $i*10);
    /* Расположение градиента определятеся случайным образом
       с помощью функции getPlace(), а размер зависит от шага */
    $grad: radial-gradient( #{$i*2}em at getPlace(),
      $current-color .3%, $transp 2.5%
    );

    $output: append($output, $grad, comma);
  }
  /* Возвращаем список градиентов для элемента */
  @return $output;
}

Функция, определяющая местоположение градиента:

@function getPlace(){
  $output: null;

  /* Горизонтальное и вертикальное положение градиента
     определяются случайным образом. Расположение звезд
     будет разным для каждого слоя */
  $h-place: getRandPerc();
  $v-place: getRandPerc();
  $output: $h-place $v-place;

  @return $output;
}

Функция, возвращающая случайное значение в процентах:

@function getRandPerc(){
  $rand-val: random();
  $perc: percentage($rand-val/100);

  /* Нужно проверить не упирается ли точка в край слоя.
     В этом случае градиент может быть обрезан
     и нужно подправить значение */
  @if( $perc < 5 ){
    $perc: $perc + 10;
  }
  @if( $perc > 95 ){
   $perc: $perc — 10;
  }

  @return $perc;
}

Функция с размерами:

@function getSizes($index: 1){
  $output: null;

  @each $sz in $sizes{
    $item: $sz;
    $output: append($output, $item*$index $item*$index, comma);
  }

  @return $output;
}

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

Буду рада, если поделитесь соображениями как можно улучшить существующий код.

Ссылки по теме:
Css Animation
Радиальные градиенты
Sass
Если вы нашли ошибку или неточность, вы можете отредактировать статью с помощью prose.io, а также можно написать мне в комментариях или в Twitter.
Система комментирования от Disqus