Будьте в курсе последних событий, подпишитесь на обновления сайта

Анимация в JavaScript

Анимация в JavaScript

Здравствуйте!  В этом уроке  рассмотрим анимации в JavaScript.  JavaScript-анимация применяется  где не подходит CSS-анимации.

Например,  анимация по сложной траектории, с  специальной временной функцией, выходящей за рамки кривых Безье, на canvas. Еще её используют для анимации в старых IE. Хотя надо сказать, что для  экономии ресурсов,  особенно мобильных устройств предпочтительно использовать CSS анимации.

Анимация в JS

Функция setInterval

Сначала давайте дадим определение  анимации . Итак с точки зрения HTML/CSS, анимация – это постепенное изменение стиля DOM-элемента. Например, увеличение координаты style.left от 0px до 100px сдвигает элемент влево.

Если увеличивать свойство  left от 0 до 100 при помощи метода  setInterval, делая по 50 изменений в секунду, то это будет выглядеть как плавное перемещение. Тот же принцип, что и в киноиндустрии: для анимации достаточно 24 или больше вызовов setInterval в секунду.

Код для анимации выглядит так:

var fps = 50; // 50 кадров в секунду 
var timer = setInterval(function() { 
if (время вышло) clearInterval(timer); 
else немного увеличить left }
, 1000 / fps)

Вот более полный пример кода анимации:

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

requestAnimationFrame

Если у вас не один такой setInterval, а несколько в разных местах кода, то браузеру нужно в те же 20 мс работать со страницей уже не один раз. А ведь кроме setInterval есть ещё другие действия, к примеру, прокрутка страницы, которую тоже надо нарисовать.

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

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

Для этого в JavaScript-фреймворках, которые поддерживают анимацию, есть единый таймер:

setInterval(function() { /* отрисовать все анимации */ }, 20);

Все анимации, которые запускает такой фреймворк, добавляются в глобальный общий список, и раз в 20 мс единый таймер проверяет его, запускает текущие, удаляет завершившиеся
Современные браузеры, кроме IE9-, поддерживают стандарт Animation timing, который представляет собой дальнейший шаг в этом направлении. Он позволяет синхронизировать все анимации со встроенными механизмами обновления страницы. То есть, сгруппированы будут не только наши, но и CSS-анимации и другие браузерные перерисовки.

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

Для этого используется функция requestAnimationFrame.

Синтаксис функции:

var requestId = requestAnimationFrame(callback)

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

// отменить запланированное выше выполнение callback cancelAnimationFrame(requestId);

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

Читайте также  Выделение на странице: Range, TextRange и Selection

Как правило, запуск callback происходит очень скоро. Если у процессора большая загрузка или батарея у ноутбука почти разряжена – то пореже.

Если вы запустите этот код, то увидите промежутки между первыми 20 запусками requestAnimationFrame. Как правило, это 10-20 мс, но бывает и больше и меньше. Это оптимальная частота анимации с точки зрения браузера.

<script>
  var prev = performance.now(); 
var times = 0; 
requestAnimationFrame(function measure(time) { 
document.body.insertAdjacentHTML("beforeEnd", Math.floor(time - prev) + " "); 
prev = time; if (times++ < 10) 
requestAnimationFrame(measure); }) 
</script>

Функция анимации на основе requestAnimationFrame:

// Рисует функция draw 
// Продолжительность анимации duration 
 function animate(draw, duration) { 
var start = performance.now(); 
requestAnimationFrame(function animate(time) { 
// определить, сколько прошло времени с начала анимации 
var timePassed = time - start; // возможно небольшое превышение времени, в этом случае зафиксировать конец 
if (timePassed > duration) timePassed = duration; // нарисовать состояние анимации в момент 
timePassed draw(timePassed); // если время анимации не закончилось - запланировать ещё кадр 
if (timePassed < duration) { requestAnimationFrame(animate); } }); }

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

На основе requestAnimationFrame можно соорудить и гораздо более мощную, но в то же время простую функцию анимации.

У анимации есть три основных параметра:

duration
Общее время, которое длиться анимация, в мс. Например, 1000.
timing(timeFraction)
Временная функция, которая, по аналогии с CSS-свойством transition-timing-function, будет по текущему времени вычислять состояние анимации.

Она получает на вход непрерывно возрастающее число timeFraction – от 0 до 1, где 0 означает самое начало анимации, а 1 – её конец.

Её результатом должно быть значение завершённости анимации, которому в CSS transitions на кривых Безье соответствует координата y.

Также по аналогии с transition-timing-function должны соблюдаться условия:

  • timing(0) = 0
  • timing(1) = 1…То есть, анимация начинается в точке (0,0) – нулевое время и нулевой прогресс и заканчивается в (1, 1) – прошло полное время, и процесс завершён.Например, функция-прямая означает равномерное развитие процесса:
    function linear(timeFraction) { return timeFraction; }
  • Её график:

linear animate js

  • Как видно, её график полностью совпадает с transition-timing-function: linear, и эффект абсолютно такой же.Есть и другие, более интересные варианты, мы рассмотрим их далее.
draw(progress)
Функция, которая получает состояние завершённости анимации и рисует его. Значению progress=0 соответствует начальная точка анимации, progress=1 – конечная.

Именно эта функция и осуществляет, собственно, анимацию.

Например, может двигать элемент:

function draw(progress) { train.style.left = progress + 'px'; }
Возможны в принципе любые варианты, анимировать можно что угодно и как угодно.

Просмотреть  пример

Временные функции

Выше мы видели самую простую, линейную временную функцию.

Рассмотрим примеры анимации движения с использованием различных timing.

В степени n

Вот еще один простой случай – progress в степени n. Частные случаи – квадратичная, кубическая функции и т.д.

Для квадратичной функции:

function quad(progress) { return Math.pow(progress, 2) }

График квадратичной функции:

quad animate js

Увеличение степени влияет на ускорение. Например, график для 5-й степени:
qunit js animation
Функция:

function circ(timeFraction) { return 1 - Math.sin(Math.acos(timeFraction)) }

График:
circ animation

Back: стреляем из лука

Эта функция работает по принципу лука: сначала мы «натягиваем тетиву», а затем «стреляем».

В отличие от предыдущих функций, эта зависит от дополнительного параметра x, который является «коэффициентом упругости». Он определяет расстояние, на которое «оттягивается тетива».

Её код:

function back(x, timeFraction) { return Math.pow(timeFraction, 2) * ((x + 1) * timeFraction - x) }

График для x = 1.5:
bsck js animation

Отскок bounce

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

Функция bounce делает то же самое, только наоборот: «подпрыгивание» начинается сразу.

Эта функция немного сложнее предыдущих и использует коэффициенты:

function bounce(timeFraction) { for (var a = 0, b = 1, result; 1; a += b, b /= 2) { 
if (timeFraction >= (7 - 4 * a) / 11) { 
return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2) } } }

Упругая анимация

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

function elastic(x, timeFraction) { return Math.pow(2, 10 * (timeFraction - 1)) * Math.cos(20 * Math.PI * x / 3 * timeFraction) }

График для x=1.5:

Реверсивные функции ease*

Итак, у нас есть коллекция временных функций.

Их  использование называется «easeIn».

Иногда нужно показать анимацию в обратном режиме. Преобразование функции, которое даёт такой эффект, называется «easeOut».

easeOut

В режиме «easeOut», значение timing вычисляется по формуле: timingEaseOut(timeFraction) = 1 — timing(1 — timeFraction)

Например, функция bounce в режиме «easeOut»:

// обычный вариант 
function bounce(timeFraction) { for (var a = 0, b = 1, result; 1; a += b, b /= 2) { 
if (timeFraction >= (7 - 4 * a) / 11) {
 return -Math.pow((11 - 6 * a - 11 * timeFraction) / 4, 2) + Math.pow(b, 2); 
    } 
  } 
} 
// преобразователь в easeOut 
function makeEaseOut(timing) { 
return function(timeFraction) { 
return 1 - timing(1 - timeFraction); 
} } 
var bounceEaseOut = makeEaseOut(bounce);

Полный пример – отскок в bounceEaseOut теперь не в начале, а в конце (и это куда красивее):
На этом графике видно преобразование easeOut изменяет поведение функции:
bounce-inout animate js

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

Красным цветом обозначен обычный вариант, а синим – easeOut.

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

easeInOut

А еще можно сделать так, чтобы показать эффект и в начале и в конце анимации. Соответствующее преобразование называется «easeInOut».

Его код выглядит так:

if (timeFraction <= 0.5) { 
// первая половина анимации) return timing(2 * timeFraction) / 2; } 
else { // вторая половина return (2 - timing(2 * (1 - timeFraction))) / 2; }

Код, который трансформирует timing:

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);

Трансформация «easeInOut» объединяет в себе два графика в один: easeIn для первой половины анимации и easeOut – для второй.
Это отлично видно, если посмотреть графики easeIn, easeOut и easeInOut на примере функции circ:

clic-ease js animate

 

  • Красным цветом обозначен обычный вариант функции circ.
  • Зелёным – easeOut.
  • Синим – easeInOut.

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

Процесс анимации полностью в ваших руках благодаря timing. Её можно сделать настолько реалистичной, насколько захочется.

Впрочем, исходя из практики, можно сказать, что варианты timing, описанные выше, покрывают 95% потребностей в анимации.

Сложные варианты step

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

Достаточно лишь написать соответствующий draw.

Набор текста

Можно, к примеру, анимировать набор текста в «скачущем» режиме:

Просмотреть  пример

Итого

Анимация выполняется путём вызовов requestAnimationFrame.  Также можно  использовать функцию setInterval, изменяя CSS свойства в единицу времени добиваться потрясающих эффектов.

Реализация анимации – очень простая и вместе с тем гибкая:

function animate(options) { 
var start = performance.now(); 
requestAnimationFrame(function animate(time) { 
// timeFraction от 0 до 1 
var timeFraction = (time - start) / options.duration; 
if (timeFraction > 1) timeFraction = 1; // текущее состояние анимации 
var progress = options.timing(timeFraction) options.draw(progress); 
if (timeFraction < 1) { requestAnimationFrame(animate); } }); }

Основные параметры:

  • duration – длительность анимации в мс.
  • timing – функция, которая определяет состояние анимации каждый кадр. Получает часть времени от 0 до 1, возвращает завершенность анимации от 0 до 1.
  • draw – функция, которая отрисовывает состояние анимации от 0 до 1.

Эту функцию можно улучшить, например добавить коллбэк complete для вызова в конце анимации.

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

Это же относится и к функции draw.

Такая реализация анимации имеет три основных области применения:

  • Нестандартные задачи и требования, не укладывающиеся в рамки CSS.
  • Поддержка IE9-.
  • Графика, рисование на canvas.

Задания

Анимируйте элемент

Сделайте так, чтобы элемент  на веб-странице подпрыгивал.

Анимируйте падение элемента  с отскоками вправо

Заставьте элемент   на веб-странице падать вправо.

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Поделиться

Об авторе

admin administrator

Сообщить об опечатке

Текст, который будет отправлен нашим редакторам: