Эта статья была рецензирована Джулио Майнарди . Спасибо всем рецензентам SitePoint за то, что сделали контент SitePoint как можно лучше!
Представьте, что ваш дизайнер попросил вас реализовать красивую покадровую анимацию для потрясающего проекта, над которым вы работали. Являясь передним разработчиком проекта, вы можете не только придумать рабочую анимацию, но и предоставить шелковистую, плавную, производительную и удобную для покадровой анимации работу, которая прекрасно работает в разных браузерах, как на рабочем столе. и мобильный.
В этом руководстве показаны различные способы создания анимации этого типа с использованием HTML, CSS и JavaScript, при этом улучшается каждая итерация для достижения наилучшего результата для вашего проекта.
Что такое покадровая анимация?
Согласно этому определению Adobe , покадровая анимация:
… Меняет содержимое сцены в каждом кадре. Лучше всего подходит для сложной анимации, в которой изображение меняется в каждом кадре, а не просто перемещается по рабочей области.
Другими словами, тема анимации представлена в виде набора изображений. Каждое изображение в наборе занимает кадр анимации, и скорость изменения, с которой каждый кадр заменяет следующий, создает иллюзию движения в изображении.
Я продемонстрирую весь рабочий процесс, поскольку вы будете работать над этой великолепной анимацией мигающего глаза на веб-сайте Zeiss .
Это набор изображений, которые вы будете использовать для создания кадров анимации:
И вот окончательный результат:
Для этого урока я решил использовать изображения SVG, потому что они отлично подходят для масштабирования с различными размерами экрана для адаптивного веб-дизайна. Однако, если по какой-либо причине вы не хотите использовать SVG-графику, вы можете создавать веб-анимации в форматах изображений PNG, JPEG и GIF или использовать HTML5 Canvas. Я поделюсь своими мыслями об этих альтернативах в конце статьи.
Для простоты, в этом уроке вы будете использовать библиотеку jQuery и будете запускать и запускать Autoprefixer , поэтому в коде не будет специфичных для браузера префиксов CSS.
Теперь давайте сделаем немного кодирования!
1 — Покадровая анимация путем изменения источника изображения
Первый вариант достаточно прост, и это одна из причин, почему мне это нравится.
В своем HTML-документе создайте элемент img
, который действует как контейнер для изображений, по одному, так как каждый кадр анимации заменяется следующим:
<img class="eye-animation" src="/images/Eye-1.svg" alt="blinking eye animation"/>
CSS:
.eye-animation { width: 300px; }
Следующим шагом является динамическая замена существующего изображения следующим в течение определенного периода времени, чтобы создать иллюзию движения.
Вы можете сделать это с помощью setTimeout
но лучше использовать requestAnimationFrame
. Это имеет некоторые преимущества с точки зрения производительности. Например:
- На качество других анимаций на экране это не повлияет
- Браузер остановит анимацию, если пользователи перейдут на другую вкладку.
Вот код jQuery:
const $element = $('.eye-animation'); const imagePath = '/images'; const totalFrames = 18; const animationDuration = 1300; const timePerFrame = animationDuration / totalFrames; let timeWhenLastUpdate; let timeFromLastUpdate; let frameNumber = 1; function step(startTime) { if (!timeWhenLastUpdate) timeWhenLastUpdate = startTime; timeFromLastUpdate = startTime - timeWhenLastUpdate; if (timeFromLastUpdate > timePerFrame) { $element.attr('src', imagePath + `/Eye-${frameNumber}.svg`); timeWhenLastUpdate = startTime; if (frameNumber >= totalFrames) { frameNumber = 1; } else { frameNumber = frameNumber + 1; } } requestAnimationFrame(step); }
Когда страница загружается, вы впервые вызываете requestAnimationFrame
и передаете ей step
функцию вместе с параметром startTime
анимации, встроенным в функцию requestAnimationFrame
.
На каждом этапе код проверяет, сколько времени прошло с момента последнего обновления источника изображения, и, если оно превышает требуемое время на кадр, оно обновляет изображение.
Поскольку вы создаете бесконечную анимацию, приведенный выше код проверяет, дошел ли он до последнего кадра, и, если это так, он сбрасывает frameNumber
в 1; если нет, то увеличивается номер frameNumber
на 1.
Изображения должны иметь одинаковую структуру имен, состоящую из увеличивающихся серий номеров и одного и того же местоположения (например, images / Eye-1.svg, images / Eye-2.svg, images / Eye-3.svg и т. Д.), Чтобы код может легко перебирать их.
Наконец, снова вызовите requestAnimationFrame
чтобы продолжить весь процесс.
Это выглядит хорошо. Однако, если вы попробуете это, вы увидите, что это не сработает, потому что при запуске анимации загружается только первое изображение. Причина этого в том, что браузер ничего не знает об изображениях, которые вы хотите отобразить, пока код не обновит атрибут src
элемента изображения в середине цикла анимации. Для плавной анимации необходимо предварительно загрузить изображения до начала цикла.
Есть разные способы сделать это. Вот тот, который мне нравится больше всего. Он состоит в добавлении скрытых элементов div
и установке их свойства background-image
для указания на требуемые изображения.
Код JQuery:
$(document).ready(() => { for (var i = 1; i < totalFrames + 1; i++) { $('body').append(`<div id="preload-image-${i}" style="background-image: url('${imagePath}/Eye-${i}.svg');"></div>`); } });
Вот полная рабочая демонстрация на CodePen:
Ниже я перечислил некоторые плюсы и минусы этого подхода.
Минусы :
- С
HTTP v1.1
необходимость загрузки нескольких изображений может значительно увеличить время загрузки страницы при первом посещении - Анимация может быть дрянной на мобильных устройствах. Это связано с тем, что браузер должен выполнять перерисовку каждый раз, когда обновляется атрибут
src
элемента image (подробности см. В блоге Пола Льюиса).
Плюсы :
- Декларативный — код просто перебирает набор изображений
- Изображение фиксируется в одном месте — не видно прыжков назад и вперед (вы поймете, почему это важно ниже).
2 — Покадровая анимация путем изменения непрозрачности изображения
Чтобы избежать перерисовки в браузере, вы можете изменить непрозрачность изображений вместо изменения их источника.
Вы можете визуализировать все изображения с opacity: 0
при загрузке страницы, а затем установить opacity: 1
именно тогда, когда вы хотите показать этот кадр.
Это повысит производительность рендеринга, но вам все равно придется предварительно загружать все изображения заранее (что может быть сложно, если у вас есть и другие изображения на странице, и вы не хотите ждать, пока все они загрузятся). Кроме того, из-за нескольких изображений у вас все равно будет больше времени загрузки первой страницы.
Вот полный код:
Вы можете избежать дублирования HTML-кода, используя возможности шаблонизаторов, таких как Pug, Twig, React, Angular и т. Д., Или просто добавляя div
используя JavaScript, как вы делали в предыдущем примере.
3 — Покадровая анимация путем изменения положения спрайта
Обычный обходной путь для предотвращения необходимости загрузки нескольких изображений заключается в использовании спрайта изображения .
Итак, давайте сделаем это.
Поместите все свои изображения в один спрайт, создав линию кадров, которая сохраняет порядок изображений, т.е. первое изображение будет самым левым, а последнее изображение будет самым правым. Затем, используя CSS-анимацию, перемещайте спрайт слева направо кадр за кадром.
HTML:
<div class="eye-animation"></div>
CSS:
.eye-animation { width: 300px; height: 300px; background-image: url('/images/blinking-eye-sprite.svg'); background-size: 1800%, 100%; background-position: left; background-repeat:no-repeat; animation-name: eye-fill; animation-duration: 1.3s; animation-timing-function: steps(17); animation-iteration-count: infinite; } @keyframes eye-fill { from { background-position: left; } to { background-position: right; } }
Приведенный выше код устанавливает свойство background-size
зависимости от количества кадров — поскольку имеется 18 кадров, оно устанавливает его на 1800%
.
Исходное положение background-position
left
таким, чтобы в начале анимации отображалось первое изображение.
Затем, с анимацией ключевых кадров , код постепенно меняет положение фона right
на время, установленное в свойстве animation-duration
( 1.3s
в приведенном выше примере).
Свойство animation-timing-function
позволяет создавать пошаговую анимацию, которая гарантирует, что пользователи не будут видеть половину одного кадра и половину следующего кадра одновременно. Это введение Криса Мабри в анимацию листов спрайтов CSS является прекрасным объяснением того, как работает этот подход.
И вам не нужен JavaScript. Ура!
Минусы :
- Он требует перерисовки браузера каждый раз, когда изменяется положение спрайта, что может привести к дергательной анимации на мобильном телефоне.
- Если ширина изображения в пикселях имеет много цифр после десятичной точки, изображение может колебаться влево и вправо во время анимации (хотя округления размера изображения до 2 цифр после десятичной точки должно быть достаточно для решения проблемы):
Плюсы :
- Нет JavaScript
- Загружается только одно изображение, что хорошо для первой загрузки страницы.
4 — Покадровая анимация путем перемещения спрайта с преобразованием
Как и ранее, теперь вы собираетесь обновить реализованное выше решение таким образом, чтобы избежать перерисовки браузера. Вы сделаете это, изменив только свойство transform
элемента, а не его background-position
.
Для этого поместите div
вы хотите анимировать, в оболочку. Это позволяет вам переводить положение всего HTML-элемента вместо его фона.
Затем поместите этот div
абсолютно внутри оболочки и начните его анимировать с помощью свойства translateX
.
Странно -94.44444444%
из-за наличия 18 слайдов и перемещения изображения только на 17 слайдов из первого ( -94.44444444%
* 100%).
HTML:
<div class="eye-animation__wrapper"> <div class="eye-animation"></div> </div>
Код Sass (не стесняйтесь использовать CSS, если хотите):
.eye-animation { width: 1800%; height: 100%; background-image: url('/images/blinking-eye-sprite.svg'); background-size: 100%, 100%; background-repeat:no-repeat; animation-name: eye-fill; animation-duration: 1.3s; animation-timing-function: steps(17); animation-iteration-count: infinite; position: absolute; left: 0; top: 0; &__wrapper { overflow: hidden; position: relative; width: 300px; height: 300px; } } @keyframes eye-fill { from { transform: translateX(0); } to { transform: translateX(-94.44444444%); } }
Это определенно выглядит лучше сейчас!
Однако в IE есть одна ошибка, которая не позволяет использовать процентные значения для свойства translate
внутри анимации. caniuse.com предоставляет пример и объяснение этого на вкладке известных проблем :
IE10 и IE11 в Windows 7 содержат ошибку, из-за которой значения преобразования translate всегда интерпретируются как пиксели при использовании в анимации.
В качестве запасного варианта вам нужно будет определить браузер с помощью JavaScript и предоставить отдельную анимацию для этого конкретного случая.
transform: translate3d(0, 0, 0)
ниже говорит браузеру переместить элемент в отдельный слой композиции, что повышает производительность рендеринга .
Вот код JavaScript:
var isIE = /Edge\/\d./i.test(navigator.userAgent) || /trident/i.test(navigator.userAgent); if (_this.isIE) { $('html').addClass('ie'); }
И код Sass:
// fallback for IE .ie { .eye-animation { transform: translate3d(0, 0, 0); animation-name: eye-fill-ie; } } @keyframes eye-fill-ie { from { left: 0; } to { left: -1700%; } }
Поиграйте с кодом в живом демо ниже:
Минусы :
- Знать о дрожании изображения, если размеры слишком точны
- Это не работает в IE, хотя доступен запасной вариант.
Плюсы :
- Загружается только одно изображение, что хорошо для первой загрузки страницы.
- Никакой анимации на мобильном телефоне, потому что не нужно перекрашивать!
Использование встроенного SVG для покадровой анимации
Одним из возможных улучшений было бы включение изображения SVG (т. Е. Сброс кода SVG прямо на страницу HTML) вместо указания на него как на внешний ресурс.
Как правило, внешние ресурсы кэшируются браузером. Таким образом, при повторном посещении браузер использует локально кэшированную версию файла, а не отправляет новый запрос на сервер.
Однако если вероятность повторного посещения страницы низкая (скажем, целевая страница), то имеет смысл использовать встроенный SVG, поскольку это уменьшает количество запросов к серверу и уменьшает время загрузки страницы в первый раз.
Sprite-Transform имеет лучшую производительность рендеринга
Чтобы быть на 100% уверенным в производительности, я создал тест производительности для всех четырех различных подходов, и вот результат, который я получил, используя Chrome:
если вы хотите поиграть с ним, вы можете воспроизвести тесты, используя любой другой браузер по вашему выбору на jsPerf.com .
5 — Почему не GIF для покадровой анимации в Интернете?
Если возможность масштабирования с различными размерами экрана не является обязательным требованием, файл GIF также может быть вариантом. Но вместе с масштабируемостью вы также потеряете возможность управлять потоком анимации, например останавливать, реверсировать или комбинировать его с другими анимациями. И вы обычно заканчиваете тем, что увеличиваете размер актива, который влияет на производительность.
Если вы хотите глубже погрузиться в причины, по которым SVG предпочтительнее GIF, прочитайте отличную статью Сары Соуэйдан на эту тему.
6 — Почему не Canvas для покадровой анимации?
Если у вас небольшой экран и несколько анимированных объектов одновременно, Canvas будет отличным выбором. Производительность потрясающая, но у этого подхода есть несколько недостатков:
- В Canvas нельзя использовать встроенные ресурсы, что означает, что это может быть не лучшим выбором для одноразовых посещенных страниц.
- Для создания такого решения необходимы знания API Canvas, что увеличивает затраты на обслуживание приложений.
- Отсутствует поддержка событий DOM — так, например, вы не могли бы что-то сделать с DOM вне элемента
canvas
после завершения анимации.
Есть еще несколько аргументов за и против Canvas по сравнению с SVG, но если вы решите пойти с Canvas, перейдите к учебнику Уильяма Мэлоуна по этой теме, который отлично объясняет, как добиться наилучших результатов.
Вывод
В настоящее время у вас есть много вариантов реализации покадровой веб-анимации. Как правило, это хорошо, но необходимость выбирать то, что работает лучше, может сбить с толку. Пункты ниже помогут вам принять правильное решение:
- Используйте SVG поверх GIF / PNG / JPEG, если вы заботитесь о масштабируемости и отзывчивости
- Анимируйте
opacity
и свойстваtransform
если вы заботитесь о производительности рендеринга - Используйте спрайты и встроенный SVG для нескольких внешних ресурсов, если вы заботитесь о производительности при первой загрузке страницы
- Используйте спрайты поверх встроенного SVG, если вы заботитесь о производительности загрузки страниц во время повторных посещений страниц
- Используйте любое решение, с которым вы и ваша команда чувствуете себя комфортно, если оно делает ваш код более читабельным и снижает затраты на обслуживание внутри группы.
Какой метод вы используете для создания покадровой веб-анимации? Дай мне знать в комментариях.