SVG-прелоадеры

В поисках что бы ещё такого сделать чтобы ездило, я захотела сделать прелоадеры на SVG.

Получилось здорово, хотя результат пока не сильно подходит для использования в реальной жизни.

Вот такие прелоадеры у меня получились:

See the Pen Thinking about SVG-preloaders by yoksel (@yoksel) on CodePen.

Такие прелоадеры могут быть использованы на любом фоне. Элементы в них могут менять не только прозрачность, но и цвет, обводку или положение в пространстве. Можно придумать огромное количество разных вариантов оформления и анимации.

Более того, использование символов позволяет как угодно менять элементы прелоадеров (сами прелоадеры, к слову, вовсе не обязательно должны быть круглыми):

See the Pen Changing items of SVG-preloaders, .v2 by yoksel (@yoksel) on CodePen.

Вместо кружочков можно вставить любые фигуры: рыбок, цветочки, снежинки, котиков… Вид прелоадера можно менять по сезону, под настроение или вовсе случайным образом.

Как сделать простой прелоадер?

Как ни странно, его можно просто написать руками, не заходя в графический редактор. Первым делом нам потребуется кружочек, напишем его:

<circle r="10" cx="10" cy="10"/>

Простая фигура без оформления, оно потом будет задано через CSS.

Чтобы явно обозначить переиспользуемый элемент, завернем его в symbol:

<symbol id="s-circle">
    <circle r="10" cx="10" cy="10"/>
</symbol>

Теперь на страницу нужно добавить нужное количество копий символа. Копии вставляются через use:

<use xlink:href="#s-circle" class="u-circle"/>

Чтобы иметь возможность манипулировать всеми копиями одновременно, завернем их в тег g:

<g id="circle" class="g-circles">
    <use xlink:href="#s-circle" class="u-circle"/>
    <use xlink:href="#s-circle" class="u-circle"/>
    <use xlink:href="#s-circle" class="u-circle"/>
    ...
</g>

Это пригодится если всей группе сразу надо будет добавить общие стили оформления или прелоадер надо будет как-то трансформировать: уменьшить, подвинуть и так далее.

Мой код в итоге выглядит вот так:

<svg viewBox="0 0 120 120" class="svg-preloader">
    <symbol id="s-circle">
        <circle r="10" cx="10" cy="10"></circle>
    </symbol>
    <g id="circle" class="g-circles">
        <use xlink:href="#s-circle" class="u-circle"/>
        <use xlink:href="#s-circle" class="u-circle"/>
        <use xlink:href="#s-circle" class="u-circle"/>
        <use xlink:href="#s-circle" class="u-circle"/>
        <use xlink:href="#s-circle" class="u-circle"/>
        <use xlink:href="#s-circle" class="u-circle"/>
        <use xlink:href="#s-circle" class="u-circle"/>
        <use xlink:href="#s-circle" class="u-circle"/>
        <use xlink:href="#s-circle" class="u-circle"/>
        <use xlink:href="#s-circle" class="u-circle"/>
        <use xlink:href="#s-circle" class="u-circle"/>
        <use xlink:href="#s-circle" class="u-circle"/>
    </g>
</svg>

Внутри тега SVG объявлен символ и ниже группа, содержащая его копии. Обратите внимание на viewBox:

viewBox="0 0 120 120"

Этот атрибут показывает размеры отображаемой области SVG-содержимого. Совершенно необходимая вещь, если вы ещё не решили какого размера должен быть ваш прелоадер: если элементу с viewBox задать ширину и высоту, содержимое изображения растянется или сожмется под заданные размеры.

Результат кода на странице выглядит вот так:

See the Pen ozKmy by yoksel (@yoksel) on CodePen.

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

Серые границы отображаемой области добавила для понятности, на самом деле границ viewBox не видно. Кстати, границы удобно делать с помощью такой фигуры:

<rect class="r-bounds" width="100%" height="100%"/>

В CSS:

.r-bounds {
    fill: none;
    stroke: #AAA;
    }

Такой прямоугольник растянется на всю отображаемую область и покажет её границы. Отображаемая область может не совпадать с границами самого SVG-элемента.

Теперь к копиям символа нужно добавить стили.

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

$max: 12; /* количество кружков */
$size: 120px; /* размер прелоадера */
$fill: orangered; /* цвет кружков */
$angle: 360/$max; /* угол поворота каждого кружка относительно центра фигуры */

Используем переменные, чтобы задать размеры прелоадера и цвет фигур:

.svg-preloader {
  height: $size;
  width: $size;
  }

.g-circles {
  fill: $fill;
  }

Результат:

See the Pen rAwgq by yoksel (@yoksel) on CodePen.

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

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

Координаты центра находим разделив на два ширину и высоту:

transform-origin: $size/2 $size/2;

Теперь с помощью транформации надо расположить кружки по кругу. У каждого кружка свой угол поворота, поэтому трансформация задается с помощью цикла @for и селектора :nth-child.

Сама трансформация состоит из трех частей:

transform: rotate(#{-$angle*$item}deg) /* угол поворота каждого отдельного кружочка */
translate(10px, 10px) /* сдвигаем каждый кружок к центру, иначе они выползут за пределы видимости */
scale(.85) /* немного уменьшаем*/

Весь код трансформаций:

.u-circle {
  transform-origin: $size/2 $size/2;

  @for $item from 1 through $max {
    &:nth-child(#{$max}n + #{$item}){
      transform: rotate(#{-$angle*$item}deg) translate(10px, 10px) scale(.85);
    }
  }
}

translate(10px, 10px) можно было бы убрать из CSS и добавить символу, но удобнее хранить все трансформации в одном месте. Также было бы удобно задавать translate(10px, 10px) scale(.85) не каждому отдельному кружку, а классу .u-circle, но, к сожалению, трансформации нельзя склеивать. То есть нельзя трансформировать элемент, а потом трансформировать его ещё раз — так не получится, потому что вторая трансформация перезапишет первую (можно сделать только некое подобие склейки, мы рассмотрим этот способ ниже, для варианта, когда склейка становится острой необходимостью).

Результат трансформаций:

See the Pen KBtme by yoksel (@yoksel) on CodePen.

Все кружки послушно расположились по кругу.

Теперь анимируем. Создаем ключевые кадры:

@keyframes opacity {
  0% {
    fill-opacity: 1;
  }
  75% {
    fill-opacity: 0;
  }
}

Добавляем анимацию в элемент:

.u-circle {
  transform-origin: $size/2 $size/2;
  animation: opacity 1.2s linear infinite;
  ...

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

@for $item from 1 through $max {
    &:nth-child(#{$max}n + #{$item}){
      transform: rotate(#{-$angle*$item}deg) translate(10px, 10px) scale(.85);
      animation-delay: -#{$item/10}s;
    }
}

Результат:

See the Pen Jgixz by yoksel (@yoksel) on CodePen.

Но что если мы хотим анимировать не только прозрачность заливки, но и цвет символов?

Добавляем ключевые кадры с изменением цвета:

@keyframes colors {
  0% {
    fill: orangered;
    }
  50% {
    fill: teal;
  }
}

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

Добавляем вторую анимацию:

.u-circle {
  transform-origin: $size/2 $size/2;
  animation: 1.2s linear infinite;
  animation-name: colors,opacity;
  ...

See the Pen nFqzd by yoksel (@yoksel) on CodePen.

На этом можно было бы и остановиться, но что если символы будут помимо цвета и прозрачности менять свое положение в пространстве?

Пишем ключевые кадры с простой анимацией:

@keyframes transform {
  50% {
    transform: scale(.5);
  }
}

Добавляем новую анимацию в список:

animation-name: colors, opacity, transform;

И на выходе получаем нечто интересное:

See the Pen JDHzK by yoksel (@yoksel) on CodePen.

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

Как сохранить положение символа на плоскости и при этом иметь возможность дополнительно его масштабировать?

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

<g class="g-circle">
    <use xlink:href="#s-circle" class="u-circle"/>
</g>

Переносим трансформацию элементов с use на группу и убираем анимацию transform:

.g-circle {
  transform-origin: $size/2 $size/2;
  animation: 1.2s linear infinite;
  animation-name: colors, opacity;
  ...

Всё работает:

See the Pen nbird by yoksel (@yoksel) on CodePen.

А теперь пробуем добавить анимацию с трансформацией. На этот раз не группе, а символам:

.u-circle {
  animation: 1.2s linear infinite;
  animation-name: transform;
}

See the Pen hDyqc by yoksel (@yoksel) on CodePen.

Получилось, кружочки никуда не улетают, но теперь им не хватает задержки анимации. Для этого нам опять понадобятся цикл и :nth-child, но цикл будет идти по группам (а не по символам), потому что символы находятся каждый в своей группе:

.g-circle {
  @for $item from 1 through $max {
    &:nth-child(#{$max}n + #{$item}) .u-circle {
       animation-delay: -#{$item/10}s;
    }
  }
}

Результат:

See the Pen Fqwpm by yoksel (@yoksel) on CodePen.

Таким образом усложнив разметку можно получить более сложные и интересные варианты анимированных трнасформаций. К символам можно добавлять две и больше трансформации без опасений что что-то поломается:

See the Pen lGLni by yoksel (@yoksel) on CodePen.

Заменить кружочки на что-то другое можно просто поменяв символ:

See the Pen bEIuo by yoksel (@yoksel) on CodePen.

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

Было бы волшебно, если бы вся эта красота просто везде работала, но всё не так просто. IE9-11 вообще не поддерживают CSS-анимации и CSS-трансформации, задаваемые SVG-элементам, то есть в них кружочки так и останутся лежать стопкой в левом верхнем углу, как в этом примере. Ещё предполагаю, что могут быть проблемы с производительностью на слабых устройствах.

Мне понравились результаты экспериментов, хотя если бы трансформы можно было складывать — код был бы изящней. Также хотелось бы более равномерной поддержки браузерами. Для широкого использования такие прелоадеры, по-моему, пока не готовы, но наверняка их можно использовать на промо-сайтах, как небольшое эффектное дополнение.

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