Статьи

Простые анимации с использованием requestAnimationFrame

Анимация элементов DOM включает изменение стиля CSS каждые несколько миллисекунд, чтобы создать иллюзию движения. Это означает передачу функции обратного вызова в setTimeout и изменение объекта style узла в этом обратном вызове. Затем снова setTimeout чтобы поставить в очередь следующий кадр анимации.

Из пепла Феникса восходит новая вспомогательная функция для записи анимации под названием requestAnimationFrame . Он начался в Firefox 4 и постепенно принимается всеми браузерами, включая IE 10. И, к счастью, легко сделать его обратно совместимым со старыми браузерами.

 window.requestAnimationFrame(callbackFunction); 

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

Просеивание через несоответствия браузера

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

 var _requestAnimationFrame = function(win, t) { return win["webkitR" + t] || win["r" + t] || win["mozR" + t] || win["msR" + t] || function(fn) { setTimeout(fn, 60) } }(window, "equestAnimationFrame"); 

Обратите внимание, как мы используем скобочную запись для доступа к свойству объекта window . Мы используем скобочную запись, потому что мы строим имя свойства на лету, используя конкатенацию строк. И если браузер не поддерживает его, мы возвращаемся к обычной функции, которая вызывает setTimeout через 60 миллисекунд для достижения аналогичного эффекта.

Сборка корпуса

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

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

 function animate() { var step = function() { _requestAnimationFrame(step); } step(); } 

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

 function animate() { var duration = 1000*3, //3 seconds end = +new Date() + duration; var step = function() { var current = +new Date(), remaining = end - current; if(remaining < 60) { //end animation here as there's less than 60 milliseconds left return; } else { var rate = 1 - remaining/duration; //do some animation } _requestAnimationFrame(step); } step(); } 

Обратите внимание, что мы делаем +new Date() чтобы получить текущее время в миллисекундах. Знак плюс приводит объект даты в числовой тип данных.

Переменная rate — это число от 0 до 1, которое представляет скорость выполнения анимации.

Делать это полезным

Теперь нам нужно подумать о входах и выходах функции. Давайте позволим функции принимать функцию и длительность в качестве параметров.

 function animate(item) { var duration = 1000*item.time, end = +new Date() + duration; var step = function() { var current = +new Date(), remaining = end - current; if(remaining < 60) { item.run(1); //1 = progress is at 100% return; } else { var rate = 1 - remaining/duration; item.run(rate); } _requestAnimationFrame(step); } step(); } 

И мы можем вызвать эту функцию так:

 animate({ time: 3, //time in seconds run: function(rate) { /* do something with rate */ } }); 

Внутри функции run я помещу некоторый код, который анимирует ширину узла от «100px» до «300px».

 animate({ time: 3, run: function(rate) { document.getElementById("box").style .width = (rate*(300 - 100) + 100) + "px"; } }); 

Улучшение варианта использования

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

 function animate(list) { var item, duration, end = 0; var step = function() { var current = +new Date(), remaining = end - current; if(remaining < 60) { if(item) item.run(1); //1 = progress is at 100% item = list.shift(); //get the next item if(item) { duration = item.time*1000; end = current + duration; item.run(0); //0 = progress is at 0% } else { return; } } else { var rate = remaining/duration; rate = 1 - Math.pow(rate, 3); //easing formula item.run(rate); } _requestAnimationFrame(step); }; step(); } 

Когда анимация запускается в первый раз, item равен нулю, а remaining составляет менее 60 миллисекунд, поэтому мы выталкиваем первый элемент из массива и начинаем его выполнять. В последнем кадре анимации remaining также меньше 60, поэтому мы заканчиваем текущую анимацию и выталкиваем следующий элемент из массива и начинаем анимировать следующий элемент.

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

Для вызова функции анимации мы делаем:

 animate([ { time: 2, run: function(rate) { document.getElementById("box").style .width = (rate*(300 - 100) + 100) + "px"; } }, { time: 2, run: function(rate) { document.getElementById("box").style .height = (rate*(300 - 100) + 100) + "px"; } } ]); 

Обратите внимание, как ширина поля увеличивается сначала за 2 секунды, а затем увеличивается высота, которая занимает еще 2 секунды.

Завершение

Давайте немного очистим наш код. Заметьте, как мы вызываем getElementById столько раз, что это уже не смешно? Давайте кешируем это и давайте кешируем начальное и конечное значения, пока мы на нем.

 animate([ { time: 2, node: document.getElementById("box"), start: 100, end: 300, run: function(rate) { this.node.style .width = (rate*(this.end - this.start) + this.start) + "px"; } } ]); 

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

И там у вас есть это. Простой помощник по анимации, который использует requestAnimationFrame с запасным вариантом для старых браузеров.

сценарий демо