Javascript как создать движущийся объект
Перейти к содержимому

Javascript как создать движущийся объект

  • автор:

Как создать анимацию в JavaScript за 30 минут

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

Мария Грегуш

Мария Грегуш

В бэкграунде — программирование, французский язык, академическое рисование, капоэйра. Сейчас учит финский. Любит путешествия и Балтийское море.

Статья подготовлена на основе вебинара с Камилем Абзаловым.

Зачем нужна анимация на сайтах

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

Как создать анимацию? Есть язык, который изначально создавался для «оживления» сайтов. Это JavaScript. Именно на нём мы и напишем вместе несколько анимаций. Мы начнём с совсем простых, а закончим одной посложнее и более практически ценной: создадим виджет Accordion (аккордеон). А потом вас ждёт небольшой бонус: сделаем анимацию в CSS3.

Какие инструменты нам понадобятся

Для работы нам понадобится библиотека jQuery (откуда её взять — расскажем чуть ниже).

Она используется на большинстве сайтов, потому что у неё много плюсов:

  • дружелюбный интерфейс;
  • она решает не только проблему анимаций, но и множество других;
  • работает на всех браузерах;
  • к ней много расширений и плагинов;
  • большое комьюнити (если у вас будут вопросы, то вы быстро найдёте на них ответы).

Сегодня мы будем работать в онлайн-редакторе CodePen. Для этого не придётся ничего устанавливать, достаточно перейти по ссылке CodePen и нажать на кнопку Start Coding. Если у вас уже есть любимый редактор, вы можете использовать его.

У вас откроется рабочее пространство с тремя колонками: HTML, CSS и JS (JavaScript):

Теперь нажмём шестерёнку возле JS — на экране появится окошко с настройками. Через строку поиска найдём там jQuery:

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

Создаём простые анимации

Анимация первая

Давайте сделаем блок, который будет реагировать на клик. Для этого сначала определим div в окошке HTML.

Что такое div? Это тег, который группирует другие элементы HTML, объединяет несколько объектов — например, блок новостей. Сейчас у нас это будет просто квадрат. Назовём мы его box, а определим вот так:

Мы использовали функцию hide (спрятать), и теперь наш квадрат исчезнет, если на него кликнуть.

Есть ещё пара похожих функций: это slideUp() и fadeOut(). Во всех случаях наш объект исчезнет. В чём же разница?

Если мы используем slideUp, то наш квадрат как бы отъедет вверх, а если fadeOut, то выцветет настолько, что станет совсем прозрачным. Можно выбрать тот вариант, который вам нравится.

Согласитесь, выглядит скудновато? Давайте добавим оформление. Для этого перейдём в окно CSS, определим там размеры нашего аккордеона (width: 450px) и отцентруем его (margin: auto). Теперь сделаем ему рамочку из чёрной линии в пиксель толщиной (border: 1px solid #222) и отступ текста в пять пикселей (padding: 5px). Конечно же, вы можете менять размеры и цвета.

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

Напоминаем, что мы всё ещё работаем в окошке CSS.

А теперь — вишенка на торте! Сделаем так, чтобы наш заголовок подчёркивался, когда мы наводим на него мышку, а курсор превращался в указатель. Поможет нам в этом селектор hover, который обозначает изменения при наведении курсора:

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

Разница в том, что вместо функции hide мы в этот раз используем toggle() — она позволяет переключать состояния. Например, если бы мы использовали show, текст бы просто показывался, а так он открывается при первом клике, а при втором опять прячется.

Линии красного цвета ведут от родителя к ребёнку, а оранжевые соединяют братьев.

Кликаем мы на текст «HTML», родитель (parent) которого — panel-header, а открыть нам надо panel-body — брата panel-header. Чтобы найти родителя, достаточно написать .parent, потому что родитель всегда один, зато братьев может быть много, поэтому мы не просто пишем .siblings, но и конкретизируем, какой именно (panel-body).

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

С hover мы уже знакомы, только добавим команду border-radius: 50%, которая превратит наш квадрат в круг:

Если вам хочется добавить движение, вы можете заставить квадрат вращаться командой rotate(360deg).

.box:hover < border-radius: 50%; transform: rotate(360deg); >

Вот и всё! Вы попробовали работать с библиотекой jQuery, сделали пару простых анимаций в JavaScript, написали виджет-аккордеон и уловили разницу с созданием анимации в CSS3.

JavaScript-анимации

С помощью JavaScript-анимаций можно делать вещи, которые нельзя реализовать на CSS.

Например, движение по сложному пути с временной функцией, отличной от кривой Безье, или canvas-анимации.

Использование setInterval

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

Например, изменение style.left от 0px до 100px – двигает элемент. И если мы будем делать это с помощью setInterval , изменяя на 2px с небольшими интервалами времени, например 50 раз в секунду, тогда изменения будут выглядеть плавными. Принцип такой же, как в кино: 24 кадров в секунду достаточно, чтобы создать эффект плавности.

Псевдокод мог бы выглядеть так:

let timer = setInterval(function() < if (animation complete) clearInterval(timer); else increase style.left by 2px >, 20); // изменять на 2px каждые 20ms, это около 50 кадров в секунду

Более детальная реализация этой анимации:

let start = Date.now(); // запомнить время начала let timer = setInterval(function() < // сколько времени прошло с начала анимации? let timePassed = Date.now() - start; if (timePassed >= 2000) < clearInterval(timer); // закончить анимацию через 2 секунды return; >// отрисовать анимацию на момент timePassed, прошедший с начала анимации draw(timePassed); >, 20); // в то время как timePassed идёт от 0 до 2000 // left изменяет значение от 0px до 400px function draw(timePassed) < train.style.left = timePassed / 5 + 'px'; >

Для просмотра примера, кликните на него:

    #train      

Использование requestAnimationFrame

Теперь давайте представим, что у нас есть несколько анимаций, работающих одновременно.

Если мы запустим их независимо с помощью setInterval(. 20) , тогда браузеру будет необходимо выполнять отрисовку гораздо чаще, чем раз в 20ms .

Это происходит из-за того, что каждая анимация имеет своё собственное время старта и «каждые 20 миллисекунд» для разных анимаций – разные. Интервалы не выравнены и у нас будет несколько независимых срабатываний в течение 20ms .

setInterval(function() < animate1(); animate2(); animate3(); >, 20)

…Меньше нагружают систему, чем три независимых функции:

setInterval(animate1, 20); // независимые анимации setInterval(animate2, 20); // в разных местах кода setInterval(animate3, 20);

Эти независимые перерисовки лучше сгруппировать вместе, тогда они будут легче для браузера, а значит – не грузить процессор и более плавно выглядеть.

Существует ещё одна вещь, про которую надо помнить: когда CPU перегружен или есть другие причины делать перерисовку реже (например, когда вкладка браузера скрыта), нам не следует делать её каждые 20ms .

Но как нам узнать об этом в JavaScript? Спецификация Animation timing описывает функцию requestAnimationFrame , которая решает все описанные проблемы и делает даже больше.

let requestId = requestAnimationFrame(callback)

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

Если в callback происходит изменение элемента, тогда оно будет сгруппировано с другими requestAnimationFrame и CSS-анимациями. Таким образом браузер выполнит один геометрический пересчёт и отрисовку, вместо нескольких.

Значение requestId может быть использовано для отмены анимации:

// отмена запланированного запуска callback cancelAnimationFrame(requestId);

Функция callback имеет один аргумент – время прошедшее с момента начала загрузки страницы в миллисекундах. Это значение может быть получено с помощью вызова performance.now().

Как правило, callback запускается очень скоро, если только не перегружен CPU или не разряжена батарея ноутбука, или у браузера нет какой-то ещё причины замедлиться.

Код ниже показывает время между первыми 10 запусками requestAnimationFrame . Обычно оно 10-20 мс:

  

Структура анимации

Теперь мы можем создать более сложную функцию анимации с помощью requestAnimationFrame :

function animate() < let start = performance.now(); requestAnimationFrame(function animate(time) < // timeFraction изменяется от 0 до 1 let timeFraction = (time - start) / duration; if (timeFraction >1) timeFraction = 1; // вычисление текущего состояния анимации let progress = timing(timeFraction); draw(progress); // отрисовать её if (timeFraction < 1) < requestAnimationFrame(animate); >>); >

Функция animate имеет три аргумента, которые описывают анимацию:

Продолжительность анимации. Например, 1000 .

Функция расчёта времени, как CSS-свойство transition-timing-function , которая будет вычислять прогресс анимации (как ось y у кривой Безье) в зависимости от прошедшего времени ( 0 в начале, 1 в конце).

Например, линейная функция значит, что анимация идёт с одной и той же скоростью:

function linear(timeFraction)

Это как если бы в transition-timing-function передать значение linear . Ниже будут представлены более интересные примеры.

Функция отрисовки, которая получает аргументом значение прогресса анимации и отрисовывает его. Значение progress=0 означает, что анимация находится в начале, и значение progress=1 – в конце.

Эта та функция, которая на самом деле и рисует анимацию.

Вот как она могла бы двигать элемент:

function draw(progress)

…Или делать что-нибудь ещё. Мы можем анимировать что угодно, как захотим.

Теперь давайте используем нашу функцию, чтобы анимировать свойство width от 0 до 100% .

Нажмите на элемент для того, чтобы посмотреть пример:

function animate() < let start = performance.now(); requestAnimationFrame(function animate(time) < let timeFraction = (time - start) / duration; if (timeFraction >1) timeFraction = 1; let progress = timing(timeFraction) draw(progress); if (timeFraction < 1) < requestAnimationFrame(animate); >>); >
     progress  elem.onclick = function() < animate(< duration: 1000, timing: function(timeFraction) < return timeFraction; >, draw: function(progress) < elem.style.width = progress * 100 + '%'; >>); >;  
animate(< duration: 1000, timing(timeFraction) < return timeFraction; >, draw(progress) < elem.style.width = progress * 100 + '%'; >>);

В отличие от CSS-анимаций, можно создать любую функцию расчёта времени и любую функцию отрисовки. Функция расчёта времени не будет ограничена только кривой Безье, а функция draw может менять не только свойства, но и создавать новые элементы (например, для создания анимации фейерверка).

Функции расчёта времени

Мы уже рассмотрели самый простой пример линейной функции расчёта времени выше.

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

Степень n

Если мы хотим ускорить анимацию, мы можем возвести progress в степень n .

Например, параболическая кривая:

function quad(timeFraction)

Если раньше анимационный эффект, такой как отскоки, был в начале, то после трансформации он будет показан в конце.

На графике выше красным цветом обозначена обычная функция и синим – после easeOut .

  • Обычный скачок – объект сначала медленно скачет внизу, а затем резко подпрыгивает вверх.
  • Обратный easeOut – объект вначале прыгает вверх, и затем скачет там.

easeInOut

Мы можем применить эффект дважды – в начале и конце анимации. Такая трансформация называется «easeInOut».

Для функции расчёта времени, анимация будет вычисляться следующим образом:

if (timeFraction else < // вторая половина анимации return (2 - timing(2 * (1 - timeFraction))) / 2; >
function makeEaseInOut(timing) < return function(timeFraction) < if (timeFraction < .5) return timing(2 * timeFraction) / 2; else return (2 - timing(2 * (1 - timeFraction))) / 2; >> bounceEaseInOut = makeEaseInOut(bounce);

В действии, bounceEaseInOut :

#brick < width: 40px; height: 20px; background: #EE6B47; position: relative; cursor: pointer; >#path
        

Функция «easeInOut» объединяет два графика в один: easeIn (обычный) для первой половины анимации и easeOut (обратный) – для второй половины.

Разница хорошо заметна, если сравнивать графики easeIn , easeOut и easeInOut для функции circ :

  • Красный обычный вариант circ ( easeIn ).
  • Зелёный – easeOut .
  • Синий – easeInOut .

Как видно, график первой половины анимации представляет собой уменьшенный easeIn , а второй – уменьшенный easeOut . В результате, анимация начинается и заканчивается одинаковым эффектом.

Более интересная функция «draw»

Вместо передвижения элемента мы можем делать что-нибудь ещё. Всё, что нам нужно – это правильно написать функцию draw .

Вот пример «скачущей» анимации набирающегося текста:

textarea < display: block; border: 1px solid #BBB; color: #444; font-size: 110%; >button
           

Итого

JavaScript может помочь в тех случаях, когда CSS не справляется или нужен жёсткий контроль над анимацией. JavaScript-анимации должны быть сделаны с помощью requestAnimationFrame . Это встроенный метод браузера, который вызывает переданную в него функцию в тот момент, когда браузер готовится совершить перерисовку (обычно это происходит быстро, но конкретные задержки зависят от браузера).

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

Вспомогательная функция animate для создания анимации:

function animate() < let start = performance.now(); requestAnimationFrame(function animate(time) < // timeFraction изменяется от 0 до 1 let timeFraction = (time - start) / duration; if (timeFraction >1) timeFraction = 1; // вычисление текущего состояния анимации let progress = timing(timeFraction); draw(progress); // отрисовать её if (timeFraction < 1) < requestAnimationFrame(animate); >>); >
  • duration – общая продолжительность анимации в миллисекундах.
  • timing – функция вычисления прогресса анимации. Получается момент времени от 0 до 1, возвращает прогресс анимации, обычно тоже от 0 до 1.
  • draw – функция отрисовки анимации.

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

JavaScript-анимации могут использовать любые функции расчёта времени. Мы рассмотрели множество примеров и их вариаций, чтобы сделать их ещё более универсальными. В отличие от CSS, мы здесь не ограничены только кривой Безье.

То же самое и с draw : мы можем анимировать всё что угодно, не только CSS-свойства.

Как работает JS: анимация средствами CSS и JavaScript

Анимация — неотъемлемая часть современных веб-интерфейсов. От того, насколько она уместна, привлекательна и производительна, зависит немалая доля впечатлений пользователя от работы с сайтом или веб-приложением. Сегодня, в переводе тринадцатой части серии материалов, посвящённых особенностям JavaScript и связанных с ним технологий, мы поговорим об анимации, выполняемой средствами CSS и JS, а также обсудим подходы к её оптимизации.

Обзор

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

Однако, анимация интерфейсов — это не так уж и просто. Что анимировать? Когда анимировать? Какие ощущения должна вызывать анимация? Поиск ответов на эти вопросы может потребовать немалых усилий.

JavaScript-анимация и CSS-анимация

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

▍CSS-анимация

CSS-анимация — это самый простой способ заставить что-либо двигаться по экрану. Начнём с простого примера, демонстрирующего перемещение элемента по осям X и Y. Делается это с помощью CSS-трансформации translate , которая настроена на длительность в 1000 мс.

.box < -webkit-transform: translate(0, 0); -webkit-transition: -webkit-transform 1000ms; transform: translate(0, 0); transition: transform 1000ms; >.box.move

При добавлении класса move значение transform меняется и начинается переход. Помимо длительности, мы можем настраивать динамику анимации ( easing ). Сущность этой настройки сводится к тому, что она влияет на то, как пользователь воспринимает анимацию. О динамике анимации мы поговорим позже.

На иллюстрации ниже показана поддержка CSS-переходов современными браузерами.

Поддержка CSS-переходов современными браузерами

Как видно, эта возможность отличается очень высоким уровнем поддержки.

Если, как и в предыдущем фрагменте кода, вы создадите отдельные CSS-классы для управления анимацией, затем можно включать и отключать анимацию средствами JavaScript.

Предположим, имеется следующий элемент.

 
Sample content.

С помощью JavaScript можно запускать и останавливать его анимацию.

var boxElements = document.getElementsByClassName('box'), boxElementsLength = boxElements.length, i; for (i = 0; i

В этом фрагменте кода мы берём все элементы, которым назначен класс box и добавляем к ним класс move для того, чтобы запустить их анимацию.

Подобные возможности совместного использования CSS — для описания анимаций, и JS — для её запуска и отключения, делают приложение хорошо сбалансированным. Разработчик может сосредоточиться на управлении состоянием элементов из JavaScript, просто назначая подходящие классы целевым элементам, позволяя браузеру самостоятельно выполнять анимации, описанные средствами CSS. Если углубиться в подобный сценарий работы с анимацией, то можно прослушивать событие transitionend элемента, но делать так стоит лишь при условии поддержки старых версий Internet Explorer.

Событие transitionend вызывается в конце перехода. Вот как с ним работать.

var boxElement = document.querySelector('.box'); // Получить первый элемент с классом box. boxElement.addEventListener('transitionend', onTransitionEnd, false); function onTransitionEnd() < // Обработать завершение перехода. >

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

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

/** * Это - упрощённая версия без префиксов * разработчиков браузеров. Если включить их сюда * (а в реальном коде это нужно), объём кода * значительно возрастёт! */ .box < /* Выберем анимацию */ animation-name: movingBox; /* Укажем длительность анимации */ animation-duration: 2300ms; /* Укажем - сколько раз мы хотим повторить анимацию */ animation-iteration-count: infinite; /* Это приводит к выполнению анимации в обратном порядке на каждой нечётной итерации */ animation-direction: alternate; >@keyframes movingBox < 0% < transform: translate(0, 0); opacity: 0.4; >25% < opacity: 0.9; >50% < transform: translate(150px, 200px); opacity: 0.2; >100% < transform: translate(40px, 30px); opacity: 0.8; >>

Вот страница, на которой показана работа этого кода.

Применяя CSS-анимации, саму анимацию описывают независимо от целевого элемента, а затем используют свойство animation-name для выбора необходимой анимации.

CSS-анимации, до сих пор, иногда требуют использования префиксов разработчиков браузеров. Так, префикс -webkit- используется в браузерах Safari, Safari Mobile, и в браузере Android. В браузерах Chrome, Opera, Internet Explorer, и Firefox анимации работают без префиксов. Для того чтобы создать CSS-код с префиксами, можно воспользоваться множеством вспомогательных инструментов, что позволяет разработчику, в исходном коде анимаций, обходиться без префиксов.

▍JavaScript-анимация

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

JS-анимации описывают в коде приложения. Как и любой другой код их можно, например, помещать в объекты. Вот пример JS-кода, который нужно написать для того, чтобы воссоздать описанный выше CSS-переход.

var boxElement = document.querySelector('.box'); var animation = boxElement.animate([ , ], 500); animation.addEventListener('finish', function() < boxElement.style.transform = 'translate(150px, 200px)'; >);

По умолчанию применение API веб-анимации модифицирует лишь внешний вид элемента. Если требуется, чтобы объект оставался в той позиции, в которую он был перемещён в ходе анимации, нужно, по завершении анимации, модифицировать его стиль. Именно поэтому в вышеприведённом примере мы прослушиваем событие finish и устанавливаем свойство элемента box.style.transform в значение translate(150px, 200px) , которое выражает то же самое, что было сделано с объектом с помощью второй трансформации, выполненной средствами JS.

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

Динамика анимации

Естественные перемещения объектов дают пользователям ощущение комфорта при работе с веб-приложениями, что ведёт к более качественному пользовательскому опыту.

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

Вот пара терминов, которые пригодятся нам при разговоре о динамике анимации. А именно, поговорим о так называемых функциях плавности. Их применение позволяет влиять на динамику анимации.

  • ease-in — это функция, при применении которой сначала анимация производится медленно, а затем — постепенно ускоряется.
  • ease-out — это функция, при использовании которой анимация начинается быстро, а потом — постепенно замедляется.

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

▍Ключевые слова для управления динамикой анимации

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

  • linear
  • ease-in
  • ease-out
  • ease-in-out

▍Анимация linear

Ключевое слово linear позволяет использовать линейную анимацию. Фактически, эта анимация описывается линейной функцией, при применении которой объект анимируется с постоянной скоростью, без ускорений и замедлений.

Вот как выглядит график линейного CSS-перехода.

Анимация linear

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

Вот как выглядит описание такой анимации:

transition: transform 500ms linear;

▍Анимация ease-out

Как уже было сказано, применение функции ease-out приводит к высокой скорости анимации в начале процесса (её скорость — выше, чем при применении линейной функции), которая замедляется в конце анимации. Вот как выглядит графическое представление такой анимации.

Анимация ease-out

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

Существует множество способов достичь подобного эффекта, но самый простой — воспользоваться ключевым словом ease-out в CSS:

transition: transform 500ms ease-out;

▍Анимация ease-in

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

Анимация ease-in

В сравнении с анимацией ease-out , анимация ease-in выглядит необычно, так как она даёт ощущение низкого уровня отзывчивости элемента из-за медленного начала. Ускорение в конце так же создаёт странные ощущения, так как скорость анимации с течением времени растёт, в то время как объекты в реальном мире, перед остановкой, обычно снижают скорость.

Для того чтобы воспользоваться этой анимацией, аналогично предыдущим, можно использовать ключевое слово ease-in :

transition: transform 500ms ease-in;

▍Анимация ease-in-out

Эта анимация является комбинацией анимаций ease-in и ease-out . Вот как выглядит её график.

Анимация ease-in-out

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

Воспользоваться этой анимацией можно с помощью ключевого слова ease-in-out :

transition: transform 500ms ease-in-out;

▍Создание собственных функций плавности

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

На самом деле, за ключевыми словами, о которых мы говорили выше ( ease-in , ease-out , linear ), стоят кривые Безье, подробности о применении которых для управления анимацией можно почитать здесь и здесь. Уделим им некоторое время, так как именно на них основано создание собственных функций плавности.

▍Кривые Безье

Для построения кривой Безье нужно четыре значения, или, говоря точнее — две пары чисел. Каждая пара описывает координаты X и Y опорной точки кубической кривой Безье. Сама кривая начинается в координате (0, 0), а заканчивается — в координате (1, 1). Настраивать можно свойства опорных точек. Значения X координат опорных точек должны находиться в диапазоне [0 ,1], значения Y также должны попадать в диапазон [0, 1], хотя надо отметить то, что спецификации не вполне проясняют этот момент.

Даже небольшое изменение значений X и Y координат опорных точек приводит к значительному изменению кривой. Взглянем на пару графиков кривых Безье, опорные точки которых имеют очень близкие, но различающиеся координаты.

Первая кривая Безье

Вторая кривая Безье

Как видите, два этих графика очень сильно отличатся друг от друга. Вот как выглядит описание второй кривой в CSS:

transition: transform 500ms cubic-bezier(0.465, 0.183, 0.153, 0.946);

Первые два числа — это координаты X и Y первой опорной точки, вторая пара — координаты второй.

Оптимизация производительности анимаций

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

Как и за всё остальное в этом мире, за анимацию надо платить. При этом анимирование некоторых свойств обходится «дешевле», чем анимирование других. Например, анимирование свойств width и height элемента приводит к изменению его геометрии и может привести к тому, что другие элементы на странице переместятся или изменят размер. Этот процесс называется формированием макета страницы. Об этом мы говорили в одном из предыдущих материалов.

В целом, следует избегать анимации свойств элементов, которые вызывают изменение макета страницы или её перерисовку. Для большинства современных браузеров это означает ограничение анимациями opacity и transform .

▍CSS-свойство will-change

CSS-свойство will-change можно использовать для того, чтобы сообщать браузеру о том, что мы намереваемся изменить свойство элемента. Это позволяет браузеру заранее, до выполнения анимации, применить подходящие оптимизации. Однако не стоит злоупотреблять свойством will-change , так как это приведёт к нерациональной трате ресурсов браузера, что, в свою очередь, приведёт к проблемам с производительностью.

Вот, например, как добавить это свойство для анимаций transform и opacity :

Данное свойство понимают пока не все браузеры, но, в браузерах Chrome, Firefox и Opera его поддержка имеется.

Поддержка CSS-свойства will-change

▍JavaScript или CSS?

Что выбрать для анимации — API веб-анимации, вызываемое из JS, или CSS? Вероятно, вы помните, что выше мы говорили, что на подобный вопрос нельзя дать однозначного ответа. Однако, для того, чтобы всё-таки определиться с технологией, учтите следующие соображения:

  • CSS-анимации и веб-анимации, при наличии их нативной поддержки, обычно обрабатываются потоком композиции (compositor thread). Он отличается от главного потока браузера (main thread), где выполняются задачи по стилизации элементов, по формированию макета, по выводу данных на экран и по выполнению JS-кода. Это означает, что если браузер выполняет какие-то сложные задачи в главном потоке, анимации будут выполняться нормально, без перерывов.
  • Анимации transforms и opacity могут быть, во многих случаях, обработаны потоком композиции.
  • Если какая-то анимация вызывает перерисовку страницы или изменение макета, поработать придётся главному потоку. Это справедливо и для CSS-анимаций, и для JS-анимаций. Дополнительная нагрузка на систему, вызванная изменением макета или перерисовкой страницы, вероятно, замедлит выполнение задач, решаемых средствами CSS или JavaScript, ставя систему в непростое положение.

▍Выбор объектов для анимации

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

▍Использование анимаций для поддержки взаимодействия с пользователем

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

▍Анимации, вызывающие большую нагрузку на систему

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

Итоги

В этом материале мы рассказали об анимации элементов веб-страниц средствами CSS и JavaScript. Анимация — мощный инструмент, поэтому обращаться с ней стоит аккуратно. При правильном подходе анимация способна значительно улучшить впечатления пользователей от работы с веб-ресурсом.

Предыдущие части цикла статей:

Уважаемые читатели! Сталкивались ли вы когда-нибудь со случаями, когда анимация по-настоящему мешает работать с каким-нибудь веб-ресурсом?

Простые анимации

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

Основные шаги анимации

Ниже перечислены необходимые шаги для того, чтобы нарисовать кадр:

  1. Очистить canvas Если фигура, которую вы собираетесь нарисовать, не занимает всю площадь canvas (как фон, например), то всё что было нарисовано ранее необходимо стереть. Проще всего это сделать при помощи метода clearRect() .
  2. Сохранить изначальное состояние canvas Если вы изменяете любые настройки (такие как стили, трансформации и т.п.), которые затрагивают состояние canvas и вы хотите убедиться, что оригинальное состояние используется каждый раз, когда был отрисован кадр, то вам следует сохранить это оригинальное состояние.
  3. Нарисовать анимированные фигуры Шаг на котором вы собственно отрисовываете кадр.
  4. Восстановить состояние canvas Если вы сохраняли состояние, восстановите его, прежде чем отрисовывать новый кадр.

Управление анимацией

Фигуры отрисовываются на canvas либо напрямую — при помощи методов canvas, либо с помощью сторонних функций. В нормальной ситуации результат станет виден на canvas после окончания выполнения скрипта. К примеру, цикл for использовать для анимации нельзя.

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

Запланированные обновления

Первый — это функции window.setInterval() (en-US), window.setTimeout() (en-US), и window.requestAnimationFrame() , которые могут быть использованы для вызова некоторой функции, через заданный промежуток времени.

Начинает периодически исполнять функцию function каждые delay миллисекунд.

Запускает выполнение указанной функции function через delay миллисекунд.

Сообщает браузеру, что вы хотите выполнить анимацию, и запрашивает, чтобы браузер вызвал указанную функцию callback для обновления анимации перед следующей перерисовкой.

Если вы не планируете никакого взаимодействия с пользователем, вы можете использовать функцию setInterval() , которая многократно выполняет, предоставленный ей код. Если же вы планируете создать игру, в которой контроль анимации осуществляется мышью или клавиатурой, то необходимо использовать setTimeout() . Установив EventListener , вы можете перехватываете любые действия пользователя и запустить соответствующие функции анимации.

Примечание: В примерах ниже мы будем использовать функцию window.requestAnimationFrame() для контроля анимации. Функция requestAnimationFrame является более эффективной для создания анимации, так как новая итерация вызывается, когда система готова к отрисовке нового кадра. Количество вызовов в секунду примерно равно 60 и уменьшается, когда вкладка неактивна. Для более подробного изучения цикла анимации, особенно для игр, прочитайте статью Анатомия видеоигр В Зоне разработке игр.

Анимированная солнечная система

В этом примере анимируется небольшая модель солнечной системы.

var sun = new Image(); var moon = new Image(); var earth = new Image(); function init()  sun.src = "canvas_sun.png"; moon.src = "canvas_moon.png"; earth.src = "canvas_earth.png"; window.requestAnimationFrame(draw); > function draw()  var ctx = document.getElementById("canvas").getContext("2d"); ctx.globalCompositeOperation = "destination-over"; ctx.clearRect(0, 0, 300, 300); // clear canvas ctx.fillStyle = "rgba(0,0,0,0.4)"; ctx.strokeStyle = "rgba(0,153,255,0.4)"; ctx.save(); ctx.translate(150, 150); // Earth var time = new Date(); ctx.rotate( ((2 * Math.PI) / 60) * time.getSeconds() + ((2 * Math.PI) / 60000) * time.getMilliseconds(), ); ctx.translate(105, 0); ctx.fillRect(0, -12, 50, 24); // Shadow ctx.drawImage(earth, -12, -12); // Moon ctx.save(); ctx.rotate( ((2 * Math.PI) / 6) * time.getSeconds() + ((2 * Math.PI) / 6000) * time.getMilliseconds(), ); ctx.translate(0, 28.5); ctx.drawImage(moon, -3.5, -3.5); ctx.restore(); ctx.restore(); ctx.beginPath(); ctx.arc(150, 150, 105, 0, Math.PI * 2, false); // Earth orbit ctx.stroke(); ctx.drawImage(sun, 0, 0, 300, 300); window.requestAnimationFrame(draw); > init(); 
canvas id="canvas" width="300" height="300">canvas> 

Анимированные часы

В этом примере создаются анимированные часы, показывающие правильное время.

function clock()  var now = new Date(); var ctx = document.getElementById("canvas").getContext("2d"); ctx.save(); ctx.clearRect(0, 0, 150, 150); ctx.translate(75, 75); ctx.scale(0.4, 0.4); ctx.rotate(-Math.PI / 2); ctx.strokeStyle = "black"; ctx.fillStyle = "white"; ctx.lineWidth = 8; ctx.lineCap = "round"; // Hour marks ctx.save(); for (var i = 0; i  12; i++)  ctx.beginPath(); ctx.rotate(Math.PI / 6); ctx.moveTo(100, 0); ctx.lineTo(120, 0); ctx.stroke(); > ctx.restore(); // Minute marks ctx.save(); ctx.lineWidth = 5; for (i = 0; i  60; i++)  if (i % 5 != 0)  ctx.beginPath(); ctx.moveTo(117, 0); ctx.lineTo(120, 0); ctx.stroke(); > ctx.rotate(Math.PI / 30); > ctx.restore(); var sec = now.getSeconds(); var min = now.getMinutes(); var hr = now.getHours(); hr = hr >= 12 ? hr - 12 : hr; ctx.fillStyle = "black"; // write Hours ctx.save(); ctx.rotate( hr * (Math.PI / 6) + (Math.PI / 360) * min + (Math.PI / 21600) * sec, ); ctx.lineWidth = 14; ctx.beginPath(); ctx.moveTo(-20, 0); ctx.lineTo(80, 0); ctx.stroke(); ctx.restore(); // write Minutes ctx.save(); ctx.rotate((Math.PI / 30) * min + (Math.PI / 1800) * sec); ctx.lineWidth = 10; ctx.beginPath(); ctx.moveTo(-28, 0); ctx.lineTo(112, 0); ctx.stroke(); ctx.restore(); // Write seconds ctx.save(); ctx.rotate((sec * Math.PI) / 30); ctx.strokeStyle = "#D40000"; ctx.fillStyle = "#D40000"; ctx.lineWidth = 6; ctx.beginPath(); ctx.moveTo(-30, 0); ctx.lineTo(83, 0); ctx.stroke(); ctx.beginPath(); ctx.arc(0, 0, 10, 0, Math.PI * 2, true); ctx.fill(); ctx.beginPath(); ctx.arc(95, 0, 10, 0, Math.PI * 2, true); ctx.stroke(); ctx.fillStyle = "rgba(0,0,0,0)"; ctx.arc(0, 0, 3, 0, Math.PI * 2, true); ctx.fill(); ctx.restore(); ctx.beginPath(); ctx.lineWidth = 14; ctx.strokeStyle = "#325FA2"; ctx.arc(0, 0, 142, 0, Math.PI * 2, true); ctx.stroke(); ctx.restore(); window.requestAnimationFrame(clock); > window.requestAnimationFrame(clock); 
canvas id="canvas" width="150" height="150">canvas> 

Зацикленная панорама

В этом примере панорама прокручивается слева направо. Мы используем фото национального парка Йосемити взятое из Википедии, но вы можете использовать любое изображение, большее элемента canvas.

var img = new Image(); // User Variables - customize these to change the image being scrolled, its // direction, and the speed. img.src = "capitan_meadows,_yosemite_national_park.jpg"; var CanvasXSize = 800; var CanvasYSize = 200; var speed = 30; //lower is faster var scale = 1.05; var y = -4.5; //vertical offset // Main program var dx = 0.75; var imgW; var imgH; var x = 0; var clearX; var clearY; var ctx; img.onload = function ()  imgW = img.width * scale; imgH = img.height * scale; if (imgW > CanvasXSize)  x = CanvasXSize - imgW; > // image larger than canvas if (imgW > CanvasXSize)  clearX = imgW; > // image larger than canvas else  clearX = CanvasXSize; > if (imgH > CanvasYSize)  clearY = imgH; > // image larger than canvas else  clearY = CanvasYSize; > //Get Canvas Element ctx = document.getElementById("canvas").getContext("2d"); //Set Refresh Rate return setInterval(draw, speed); >; function draw()  //Clear Canvas ctx.clearRect(0, 0, clearX, clearY); //If image is if (imgW  CanvasXSize)  //reset, start from beginning if (x > CanvasXSize)  x = 0; > //draw aditional image if (x > CanvasXSize - imgW)  ctx.drawImage(img, x - CanvasXSize + 1, y, imgW, imgH); > > //If image is > Canvas Size else  //reset, start from beginning if (x > CanvasXSize)  x = CanvasXSize - imgW; > //draw aditional image if (x > CanvasXSize - imgW)  ctx.drawImage(img, x - imgW + 1, y, imgW, imgH); > > //draw image ctx.drawImage(img, x, y, imgW, imgH); //amount to move x += dx; > 

Заметьте, что ширина и высота должны совпадать со значениями CanvasXZSize и CanvasYSize .

canvas id="canvas" width="800" height="200">canvas> 

Другие примеры

Хороший пример того, как сделать управляемую анимацию с клавиатуры.

Мы рассмотрим некоторые продвинутые методы анимации и физику в следующей главе.

  • « Предыдущая статья
  • Следующая статья »

Found a content problem with this page?

  • Edit the page on GitHub.
  • Report the content issue.
  • View the source on GitHub.

This page was last modified on 3 авг. 2023 г. by MDN contributors.

Your blueprint for a better internet.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *