Сегодня мы собираемся сделать много вещей, связанных с анимацией при помощи CSS. Точнее, мы будем заниматься сегодня спиннерами. Да-да)) Ими мы еще с вами не занимались. Какое упущение с моей стороны! Итак, что скажете? Готовы?
Несколько слов, перед тем как продолжить:
- Мне хотелось показать показать потенциал CSS, в частности CSS3, который в большинстве своем не работает на IE9-, поэтому, если вы планируете поддерживать это старье, не забудьте сделать альтернативные варианты.
- Лично я использую бокс-модель, в которой [ширина] = [ширина элемента] + [внутренние горизонтальные отступы] + [толщина границ по бокам]. Активировать ее можно следующим кодом:
*,
*:before,
*:after {
box-sizing: border-box;
}
CSS-анимация: "за" и "против"
Каковы преимущества и недостатки прелоадеров на чистом CSS? Почему бы воспользоваться решениями на Javascript, или даже старым добрым способом: анимированными GIF? Так вот, однозначного ответа на этот вопрос нет, все будет зависеть от ситуации. Но позвольте мне дать вам некоторые идеи.
"За"
- CSS-анимация легко редактируема: вы можете быстро менять длительность, скорость, цвет, что угодно!
- Вы можете применять масштабирование без потери качества
- Анимация с помощью CSS быстрее, чем анимация с помощью Javascript, так как использует родной движок браузера
- CSS анимации используют GPU ускорение: если у вас хороший графический процессор на видеокарте, вы будете иметь очень быстрые и плавные анимации
- CSS анимацию можно легко остановить с помощью свойства propertyanimation-play-state
"Против"
- CSS анимации поддерживается не всеми браузерами: IE9 и Opera Mini не понимают эти инструкции
- Прелоадеры \ индикаторы загрузки могут состоять из громоздкого кода, который не всегда того стоит
- Анимации CSS не могут быть так просто привязаны к pointer-events (кроме наведения мышкой) с помощью JavaScript
Определить поддерживает ли данный браузер анимацию с помощью CSS можно с помощью библиотеки Modernizr и если нет, использовать изображение в формать GIF.
Пример 1
Мы начнем с чем-то довольно просто, как всегда. Наша маленькая сфера неустанно работает бегая туда-сюда, слева направо. Анимация здесь не прихоть, наоборот, она игдает важную роль, давая пользователю понять, что приложение приступило к обработке данных.
Наш HTML код прост как никогда
<div class="bar">
<i class="sphere"></i>
</div>
Какой-то минимализм прямо! We could even do this we a single element but it can easily make the CSS ugly. Note that we could have both, clean markup and nice CSS when we’ll be able to animate pseudo-elements. ;)
CSS
Мы создаем контейнер для сферы: им будет служить тег с классом bar
. Чтобы сохранить пропорции и сделать прелоадер масштабируемым, я использовал единицы измерения em
. Вы можете легко изменить значение размера шрифта в контейнере, масштабирую его так, как вам хочется. Вы еще не зеваете?))
.demo-1 .bar {
/* Размер и позиционирование */
font-size: 20px; /* 1em */
width: 10em;
height: 1em;
position: relative;
margin: 100px auto;
/* Стилизация */
border-radius: .5em; /* Height/2 */
background: white; /* Fallback */
background: rgba(255,255,255,0.6);
box-shadow:
0 0 0 .05em rgba(100,100,100,0.075), /* Тонкие границы */
0 0 0 .25em rgba(0,0,0,0.1), /* Внешние границы */
inset 0 .1em .05em rgba(0,0,0,0.1), /* Добавим тень внутрь*/
0 .05em rgba(255,255,255,0.7); /* Небольшое отражение */
}
Давайте добавим надпись "Подождите...". Вы, наверное, заметили, ее нет в нашем HTML коде, хотя это контент. На самом деле, если все делать по правилам, она действительно должна быть в HTML разметке, так как это содержание, а не графика. Но для примера, я решил, что не стоит заморачиваться:
.demo-1 .bar:after {
/* Содержание и позиционирование */
content: "Подождите...";
position: absolute;
left: 25%;
top: 150%;
/* Слили для текста */
font-family: 'Carrois Gothic', sans-serif;
font-size: 1em;
color: #555;
text-shadow: 0 .05em rgba(255,255,255,0.7);
}
Теперь займемся нашей сферой.
.demo-1 .sphere {
/* Размер */
display: block;
width: 1em;
height: 100%;
/* Стилизация */
border-radius: 50%;
background: linear-gradient(#eee, #ddd);
box-shadow:
inset 0 .15em .1em rgba(255,255,255,0.3), /* Сверху светлая */
inset 0 -.1em .15em rgba(0,0,0,0.15), /* Снизу темная */
0 0 .25em rgba(0,0,0,0.3); /* Внешний тень */
/* Анимация */
animation: move 1.75s ease-in-out infinite alternate;
}
Ключевые кадры для нашей анимации:
@keyframes move {
to { margin-left: 90%; }
}
How could it be easier, right? So the sphere
element runs the move
animation in 1.75 seconds alternatively from start to end and end to start.
Примечание: простым способом преобразовать этот спиннер в индикатор прогресса можно, если удалить параметр alternate
в описании анимации и установить такое время для анимации, какое будет ждать пользователь. Если вы хотите динамически обновлять позицию элемента на основе прогрессии загрузки, воспользуйтесь JavaScript.
Пример 2
Теперь давайте продолжим с чем-то более сложным и приятным.
Этот эффект полностью построен на основе псевдоэлемента. Дополнительный код здесь не нужен:
<div class="spinner"></div>
CSS
Тут все просто, поверьте мне. Во-первых, сам элемент. Я использовал красный и бежевый, но вы могли бы выбрать любой понравившийся вам цвет. То же самое для количества цветов, я выбрал два, но вы могли бы сделать три или четыре. Или только один. Да сколько угодно!
.spinner {
/* Размер и позиционирование */
font-size: 100px; /* 1em */
width: 1em;
height: 1em;
position: relative;
margin: 100px auto;
/* Styles */
border-radius: 50%;
background: #FF4F72; /* для старых браузеров */
background:
linear-gradient(#ea2d0e 50%, #fcd883 50%), /* Первая колонка */
linear-gradient(#fcd883 50%, #ea2d0e 50%); /* Вторая колонка */
background-position:
0 0, /* Позиция 1й колонки */
100% 0; /* Позиция 2й колонки */
background-size: 50% 100%; /* Сократили запись "50% 100%, 50% 100%" */
background-repeat: no-repeat;
box-shadow:
inset 0 0 0 .12em rgba(0,0,0,0.2), /* Внутренняя граница */
0 0 0 .12em rgba(255,255,255,0.1); /* Кнешняя граница */
opacity: 0.7;
animation: rota 3s infinite alternate;
}
А теперь псевдоэлемент для внутреннего прозрачного белого круга:
.spinner:after {
/* Size */
content: "";
width: 50%;
height: 50%;
/* Perfect centering */
position: absolute;
top: 25%;
left: 25%;
/* Styles */
border: .12em solid rgba(255,255,255,0.3);
border-radius: inherit;
}
И наша анимация
@keyframes rota {
25% { transform: rotate(270deg); }
50% { transform: rotate( 90deg); }
75% { transform: rotate(360deg); }
100% { transform: rotate(180deg); }
}
Пример 3
Теперь давайте сделаем что-нибудь более сложное. В плане эксперимента, я пытался имитировать случайное поведение в CSS. Увы и ах: это невозможно. Даже если вы установите абсолютно произвольные значения, никакой хаотичности не будет. Во всяком случае, мы увидим это с вами чуть позже.
HTML код в этом примере не блещет красотой и изяществом. Поскольку мы не можем анимировать псевдо-элементы, нам придется использовать несколько элементов. Я решил обойтись списком (даже если это не очень семантично), но вы, взяв идею за основу, можете сделать так, как хотите.
<ul class="spinner">
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
CSS
Первое, что нужно сделать - это стилизовать сам список...
.spinner {
/* Размер и позиционирование */
font-size: 100px; /* 1em */
width: 1em;
height: 1em;
margin: 100px auto;
position: relative;
/* Стилизация */
list-style: none;
border-radius: 50%;
/* Тонкая белая линия вокруг наших точек */
border: .01em solid rgba(150,150,150,0.1);
}
...и общие свойства для всех элементов списка.
.spinner li {
width: .2em;
height: .2em;
position: absolute;
border-radius: 50%;
}
При загрузке страницы мы увидим четыре точки, не перекрывающие друг друга. Они расположены в четырех частях света: на севере, юге, западе и востоке. Центры их вращения совпадают с центром нашего спиннера.
.spinner li:nth-child(1) {
background: #00C176; /* Синяя */
top: 0;
left: 50%;
margin-left: -.1em; /* ширина/2 */
transform-origin: 50% 250%;
animation:
rota 1.13s linear infinite,
opa 3.67s ease-in-out infinite alternate;
}
.spinner li:nth-child(2) {
background: #FF003C; /* Красная */
top: 50%;
right: 0;
margin-top: -.1em; /* высота/2 */
transform-origin: -150% 50%;
animation:
rota 1.86s linear infinite,
opa 4.29s ease-in-out infinite alternate;
}
.spinner li:nth-child(3) {
background: #FABE28; /* Желтая */
bottom: 0;
left: 50%;
margin-left: -.1em; /* ширина/2 */
transform-origin: 50% -150%;
animation:
rota 1.45s linear infinite,
opa 5.12s ease-in-out infinite alternate;
}
.spinner li:nth-child(4) {
background: #88C100; /* Зеленая */
top: 50%;
left 0;
margin-top -.1em; /* высота/2 */
transform-origin: 250% 50%;
animation:
rota 1.72s linear infinite,
opa 5.25s ease-in-out infinite alternate;
}
Последнее, что останется сделать - это написать сценарий для двух анимаций. Первая описывает вращение, а вторая - изменение прозрачности.
@keyframes rota {
to { transform: rotate(360deg); }
}
@keyframes opa {
12.0% { opacity: 0.80; }
19.5% { opacity: 0.88; }
37.2% { opacity: 0.64; }
40.5% { opacity: 0.52; }
52.7% { opacity: 0.69; }
60.2% { opacity: 0.60; }
66.6% { opacity: 0.52; }
70.0% { opacity: 0.63; }
79.9% { opacity: 0.60; }
84.2% { opacity: 0.75; }
91.0% { opacity: 0.87; }
}
Примечание: я показал это творение своим друзьям и обнаружил, что некоторые люди чувствуют себя неловко, когда предметы вращаются против часовой стрелки. С другой стороны, когда движение происходит по часовой стрелке кажется, что загрузка происходит быстрее.
А что не так с цифрами, - спросите вы? Как я уже говорил вам, что я пытался сделать движение непредсказуемым. Я мог бы сделать это с помощью скрипта на Javascript - сгенерить случайные чисела и вставить их в CSS, но мне эта идея не понравилась. Я сделал по другому. Я сам придумал эти "случайные" числа, вот почему вы можете увидеть в CSS коде такие странные анимационные длительности: 1.72s, 4.29s или 1.13s. То же самое и с ключевыми кадрами для величин непрозрачностей дляopa
. Вот такой вот "псевдо рандом"))
А что насчет transform-origin
, - снова спросите вы?
Сейчас я расскажу вам о небольшом трюке, который поможет вам найти нужное значение для transform-origin, если вы захотите добавить вращение, которое не выполняется вокруг дефолтного центра вращения.
Главная проблема, с которой я столкнулся в отношении свойства transform-origin – это недопонимание самого процесса. Я так и не понял до конца (и до сих пор не всегда понимаю), как это происходит.
В любом случае, суть заключается в том, чтобы отобразить transform origin с помощью псевдоэлемента. Взгляните:
.my-element {
transform-origin: 12% 34%;
}
.my-element:after {
content: "";
width: 4px;
height: 4px;
position: absolute;
left: 12%; /* первое значение transform-origin */
top: 34%; /* второе значение transform-origin */
margin: -2px 0 0 -2px;
border-radius: 50%;
background: red;
}
Пример 4
Давайте продолжим наши изыски и создадим нечто в стиле спим панк: колеса, шестерни и т.д. Думаю, вам понравится. Начнем.
Тоже непросто, так как нам нужно будет заключить каждую букву в контейнер при вращении. И нам потребуется контейнер, чтобы отменить вращение главного элемента. Мы поговорим об этом позже.
<div class="wrapper">
<div class="inner">
<span>З</span>
<span>а</span>
<span>г</span>
<span>р</span>
<span>у</span>
<span>з</span>
<span>к</span>
<span>а</span>
</div>
</div>
CSS
У нас есть много свойств, которые мы должны применить к контейнеру: размер, позиционирование, настройки для шрифта, анимация и т.д./p>
.wrapper {
/* Hазмер и позиционирование */
font-size: 25px; /* 1em */
width: 8em;
height: 8em;
margin: 100px auto;
position: relative;
/* Стилизация */
border-radius: 50%;
background: rgba(255,255,255,0.1);
border: 1em dashed rgba(138,189,195,0.5);
box-shadow:
inset 0 0 2em rgba(255,255,255,0.3),
0 0 0 0.7em rgba(255,255,255,0.3);
animation: rota 3.5s linear infinite;
/* Стили для текста */
font-family: 'Racing Sans One', sans-serif;
color: #444;
text-align: center;
text-transform: uppercase;
text-shadow: 0 .04em rgba(255,255,255,0.9);
line-height: 6em;
}
Вау, мы сделали это! Это было непросто, не так ли? Теперь нам нужно сделать внутренние колеса из псевдо-элементов.
.wrapper:before,
.wrapper:after {
content: "";
position: absolute;
z-index: -1;
border-radius: inherit;
box-shadow: inset 0 0 2em rgba(255,255,255,0.3);
border: 1em dashed;
}
.wrapper:before {
border-color: rgba(138,189,195,0.2);
top: 0; right: 0; bottom: 0; left: 0;
}
.wrapper:after {
border-color: rgba(138,189,195,0.4);
top: 1em; right: 1em; bottom: 1em; left: 1em;
}
Теперь внутренний контейнер inner
и стили для тегов span
. Обратите внимание, как мы используем параметр reverse
для контейнера inner
, чтобы отменить вращение контейнера wrapper
.
.wrapper .inner {
width: 100%;
height: 100%;
animation: rota 3.5s linear reverse infinite;
}
.demo-4 .wrapper span {
display: inline-block;
animation: placeholder 1.5s ease-out infinite;
}
.wrapper span:nth-child(1) {animation-name: loading-1;}
.wrapper span:nth-child(2) {animation-name: loading-2;}
.wrapper span:nth-child(3) {animation-name: loading-3;}
.wrapper span:nth-child(4) {animation-name: loading-4;}
.wrapper span:nth-child(5) {animation-name: loading-5;}
.wrapper span:nth-child(6) {animation-name: loading-6;}
.wrapper span:nth-child(7) {animation-name: loading-7;}
.wrapper span:nth-child(8) {animation-name: loading-8;}
К сожалению, мы должны задавать анимацию для каждой буквы, так как они начинаются не одновременно. Сначала я думал, использовать задержку для анимации, но это свойство работает только при первом запуске.
@keyframes rota {
to { transform: rotate(360deg); }
}
@keyframes loading-1 {
12.5% { opacity: 0.3; }
}
@keyframes loading-2 {
25% { opacity: 0.3; }
}
@keyframes loading-3 {
37.5% { opacity: 0.3; }
}
@keyframes loading-4 {
50% { opacity: 0.3; }
}
@keyframes loading-5 {
62.5% { opacity: 0.3; }
}
@keyframes loading-6 {
75% { opacity: 0.3; }
}
@keyframes loading-7 {
87.5% { opacity: 0.3; }
}
@keyframes loading-8 {
100% { opacity: 0.3; }
}
Эти анимации могут быть немного сложнее для понимания, поэтому я задержусь на них чуть подольше. Итак, каждая буква должна:
- Немного изменить свою прозрачность
- Стать снова непрозрачной
- Подождать, пока все остальные буквы не сделают то же самое
- Вернуться к 1 шагу и повторить все по новой
Как мы это делаем? Смотрите:
- Подсчитайте число букв в вашем слове. В моем примере их 8.
- Вы делите 100 (число ключевых кадров, выраженное в %) на это число. Получается 12,5%.
- Каждые 14.28 кадра запускается анимация буквы.
- Готово.
Пример 5
Давайте закончим нашу статью чем-то более концептуальным. Я знаю, что люди не любят сложные вещи, но, кто знает, вдруг вам это когда-нибудь пригодится. Несмотря на кажущуюся сложность нам понадобится всего один элемент.
<div class="pre-loader"></div>
CSS
На самом деле, наш элемент - это одна из наших маленьких сфер. Семь других сделаны из бокс-тени.
.pre-loader {
/* Размер и позиционирование */
font-size: 30px; /* 1em */
width: 1em;
height: 1em;
position: relative;
margin: 100px auto;
/* Стилизация */
border-radius: 50%;
background: #123456;
transform-origin: 50% 250%;
animation:
blink 1s steps(1, start) infinite, /* Мигание */
counter-clock 8s linear infinite; /* Вращение */
/* Точки, вращающиеся по часовой стрелке */
box-shadow:
1em 1em #123456,
2em 2em #123456,
1em 3em #123456,
0em 4em #123456,
-1em 3em #123456,
-2em 2em #123456,
-1em 1em #123456;
}
Эффект прозрачного квадрата достигается с помощью… ну, прозрачного квадрата. Это вращающийся псевдоэлемент, расположенный сверху всего остального. Все очень просто.
.pre-loader:after {
/* Размер и позиционироание */
content: "";
width: 3em;
height: 3em;
position: absolute;
left: -1em;
top: 1em;
/* Стилизация */
transform: rotate(45deg);
background: white;
background: rgba(255,255,255,0.6);
}
А теперь анимация. A few explanations about the second one (blink
):
- У нас есть 8 кружочков, поэтому мы разделим 100 ключевых кадров на 8 и получим 12.5
- Каждые 12,5 ключевых кадров, кружок изменяет свою непрозрачность.
RGB (18,52,86)
это код для #123456, записанный в RGB формате.
- В первом кадре (12,5%), это сам элемент, который снижает прозрачность.
@keyframes counter-clock {
to { transform: rotate(-360deg); }
}
@keyframes blink {
12.5% {
background: rgba(18,52,86,0.6);
box-shadow:
1em 1em #123456,
2em 2em #123456,
1em 3em #123456,
0em 4em #123456,
-1em 3em #123456,
-2em 2em #123456,
-1em 1em #123456;
}
25% {
background: #123456;
box-shadow:
1em 1em rgba(18,52,86,0.6),
2em 2em #123456,
1em 3em #123456,
0em 4em #123456,
-1em 3em #123456,
-2em 2em #123456,
-1em 1em #123456;
}
37.5% {
background: #123456;
box-shadow:
1em 1em #123456,
2em 2em rgba(18,52,86,0.6),
1em 3em #123456,
0em 4em #123456,
-1em 3em #123456,
-2em 2em #123456,
-1em 1em #123456;
}
50% {
background: #123456;
box-shadow:
1em 1em #123456,
2em 2em #123456,
1em 3em rgba(18,52,86,0.6),
0em 4em #123456,
-1em 3em #123456,
-2em 2em #123456,
-1em 1em #123456;
}
62.5% {
background: #123456;
box-shadow:
1em 1em #123456,
2em 2em #123456,
1em 3em #123456,
0em 4em rgba(18,52,86,0.6),
-1em 3em #123456,
-2em 2em #123456,
-1em 1em #123456;
}
75% {
background: #123456;
box-shadow:
1em 1em #123456,
2em 2em #123456,
1em 3em #123456,
0em 4em #123456,
-1em 3em rgba(18,52,86,0.6),
-2em 2em #123456,
-1em 1em #123456;
}
87.5% {
background: #123456;
box-shadow:
1em 1em #123456,
2em 2em #123456,
1em 3em #123456,
0em 4em #123456,
-1em 3em #123456,
-2em 2em rgba(18,52,86,0.6),
-1em 1em #123456;
}
100% {
background: #123456;
box-shadow:
1em 1em #123456,
2em 2em #123456,
1em 3em #123456,
0em 4em #123456,
-1em 3em #123456,
-2em 2em #123456,
-1em 1em rgba(18,52,86,0.6);
}
}
Примечание: я знаю, что я уже говорил о том, что вращение против часовой стрелки негативно воспринимаются людьми, но так мы его совмещаем с быстрой анимацией по часовой стрелке, то отрицательной реакции не возникает.
Упрощения с помощью библиотек
Как вы, возможно, заметили в некоторых примерах, иногда, мы должны были повторять одни и те же фрагменты кода для различных значений, или действий, что не может не раздражать. К счастью, мы можем значительно облегчить себе работу с помощью соответствующих инструментов. Я думаю, вы уже догадались, что речь идет о CSS препроцессорах. На самом деле не только о них. В 4 примере был самый большой повторяющийся код. Давайте попробуем записать его покомпактнее, скажем, автоматизируем процесс для изменения количества букв в слове.
Lettering.js
Lettering.js does a very simple thing: it wraps every letter in the targeted element with span
tags.
Таким образом, с этого момента, мы не должны вручную обертывать наши символы тегами span
, Lettering.js делает это за нас. Довольно просто, не так ли? Но нас куда больше волнует CSS, не так ли?
Как вы уже наверняка поняли, нам нужен цикл loop. И у меня есть хорошие новости, CSS препроцессоры поддерживают это! По крайней мере, некоторые из них... В частности, Sass поддерживает цикл loop, а вот LESS уже не обеспечивает поддержку цикла. Вместо этого, вы должны сделать рекурсивную функцию и передавать в нее уменьшающееся значение. Жесть конечно, но работает.
/* SCSS */
for $i from 1 through 10 {
/* Какие-то действия */
}
/* LESS */
.loop(@index) when (@index > 0) {
/* Какие-то действия */
.loop(@index - 1);
}
.loop(0) { }
.loop(10);
Видите? Рекурсивная функция может заменить нам цикл. Итак, для нашего 4 примера:
/* SCSS */
$iterations : 8; //Число букв в слове
@for $i from 1 through $iterations {
$val : 100%/$iterations*$i;
@keyframes loading-#{$i} {
#{$val} {
opacity: 0.3;
}
}
.wrapper span:nth-child(#{$i}) {
animation-name: loading-#{$i};
}
}
/* LESS */
@iterations: 8; //Число букв в слове
@newline: `"\n"`; //Небольшой фикс
.loop (@index) when (@index > 0) {
@val: 100/@iterations*@index;
(~"@keyframes loading-@{index} {@{newline}@{val}%") {
opacity: 0.3;
}
(~"} .wrapper span:nth-child(@{index}) @{newline}") {
animation-name: "loading-@{index}";
}
.loop(@index - 1);
}
.loop (0) {}
.loop (@iterations);
На выходе получим тоже самое, что и при использовании чистого CSS. Но то, что кажется быстрым и простым когда у вас есть только 8 букв, может показаться сложным и громоздким, если у вас 40 или 50 символов. В этом случае использование CSS препроцессоров значительно экономит время.
Добавить префиксы к версии SCSS довольно просто: продублируйте строчку с названием анимации и блок @keyframes и добавьте префиксы. Однако в случае с LESS все не так просто, поэтому я сэкономлю вам время и покажу конечный результат
Удачи!