Каждый, кто хоть немного пользуется javascript, пытался реализовать анимацию. Это могут быть меню-аккордеоны, выпадающие меню, что-то движущиеся, сделанное на canvas или svg или в крайнем случае снег, который летает на доброй половине сайтов перед новым годом ( не нравится мне он :P ). Лучшим решением в случае анимации, бесспорно будет css transitions и animations. Но, к сожалению на момент написания статьи, ie9 (последняя версия ослика) ничего о css анимации не знает, да и в некоторых случаях (анимация на canvas например), css никак не поможет.
Давайте рассмотрим типовую функцию, которую мы используем, когда реализуем анимацию:
Javascript
var step = function(){
// описываем один шаг
}
setInterval(step, 100); // повторяем шаг через наждые 0.1с
или так:
Javascript
var step = function(){
setTimeout(step, 100); // циклически выполняем шаги один за одним, вызывая функцию саму из себя
// описываем один шаг тут
}
step(); // выполняем шаг в первый раз
setInterval и частота кадров
Плавность анимации зависит от частоты кадров. Частота кадров измеряется в «кадрах в секунду» (FPS — frame per second). Фильмы и видео обычно делают с 24fps — 30fps. Чем больше это число, тем более гладкой кажется анимация. С другой стороны — чем больше fps, тем больше требуется ресурсов процессора, что может привести к подвисанию и пропуску кадров. Поскольку большинство экранов имеет частоту обновления 60 Гц — fps к которому надо стремится равен 60-ти.
Немного математики и идеальный интервал 1000ms / 60(fps) = 16.7ms
т.е.
Javascript
setInterval(step, 17);
И что? В чем подвох?
Что не так с этой функцией?
Во-первых, SetTimeout не принимает во внимание то, что еще происходит в браузере. Страница может, например, находится в неактивной вкладке браузера. При этом она будет использовать ресурсы процессора не взирая на это.
Кстати умный Chrome делает setInterval
и setTimeout
равным 1fps
в скрытых вкладках.. но, насколько я знаю, так пока делает только chrome :(
Во-вторых, SetTimeout требует перерисовки страницы не в то же время, когда это делает ваш компьютер (а он делает это регулярно). Это означает, что ваш бедный браузер должен синхронизировать вашу горе-анимацию с обновлением всего экрана, и если ее частота не синхронизирована с обновлением всего экрана, это может потребовать больше вычислительной мощности. А это загрузка процессора, нагрев, шум куллера, расход батареи мобильных девайсов.. и т.п
Еще одним аспектом является анимации нескольких элементов сразу. Конечно, можно попытаться все это синхронизировать, но .. это очередной кошмар, в случае разноплановой анимации, происходящей одновремменно.
requestAnimationFrame — путь к спасению!
Чтобы преодолеть эти проблемы, Mozilla (создатели Firefox) предложил функцию requestAnimationFrame, которая впоследствии была принята и усовершенствована командой WebKit (Chrome и Safari). Она обеспечивает встроеный API для запуска любых типов анимации в браузере (DOM элементов, canvas, WebGL и др.)
Javascript
function step() {
requestAnimationFrame(step);
// описываем один шаганимации тут
}
step();
Шикарно! Это же, как setTimeout
, приведенный выше, но с requestAnimationFrame
вместо него!!! При желании вы можете передать еще один параметр — анимируемый элемент: requestAnimationFrame(step, element);
Однако, как вы могли заметить, мы не указали интервал. Как часто бдет вызываться наша функция? Все зависит от частоты кадров вашего браузера и компьютера (обычно это 60 кадров в секунду). Ключевым отличием является то, что вы просите браузер выполнить функцию (в нашем примере step
) при первой возможности, а не с заданным интервалом. Еще одно достоинство такого подхода в том, что браузеры могут снизить requestAnimationFrame в зависимости от нагрузки, видимости элемента и состояния батареи.
Еще несколько неоценимых достоинств requestAnimationFrame
, в том, что он будет группировать все ваши анимации в одном браузерном repaint
. Это сэкономит ресурсы процессора и позволяет вашему устройству быть быстрее и жить дольше.
Если вы используете requestAnimationFrame, все ваши анимации будут гладкими и красивыми, синхронизированными с вашим графическим процессором (GPU) и съедающими гораздо меньше ресурсов центрального процессора (CPU).
Ух ты! Клево! И что все так безоблачно? .. или есть проблемы?
Ну, да, проблемы пока есть, но совсем пустяковые. Это совсем новый API и.. для Chrome и Safari — это webkitRequestAnimationFrame
. Соответственно в Firefox это mozRequestAnimationFrame
. Microsoft (Internet Explorer 10) будет поддерживать msRequestAnimationFrame
.
Чтобы справится с этим, Eric Möller (Opera), Paul Irish (Google) и Tino Zijdel (Tweakers.net) создали polyfill приведенный ниже:
Javascript
(function() {
var lastTime = 0;
var vendors = ['ms', 'moz', 'webkit', 'o'];
for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) {
window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame'];
window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame']
|| window[vendors[x]+'CancelRequestAnimationFrame'];
}
if (!window.requestAnimationFrame)
window.requestAnimationFrame = function(callback, element) {
var currTime = new Date().getTime();
var timeToCall = Math.max(0, 16 - (currTime - lastTime));
var id = window.setTimeout(function() { callback(currTime + timeToCall); },
timeToCall);
lastTime = currTime + timeToCall;
return id;
};
if (!window.cancelAnimationFrame)
window.cancelAnimationFrame = function(id) {
clearTimeout(id);
};
}());
Вставляем в начало кода и requestAnimationFrame
работает как родной. В браузерах, где он вообще не поддерживается, будет изпользоваться setInterval
Но что делать, если я хочу установить частоту кадров?
Проблемы заключается в том, как контролировать fps
для requestAnimationFrame
, если вы не можете указать частоту кадров? Это критично для многих веб приложений и особенно для игр.
Вот техника, которую вы можете использовать:
Javascript
var fps = 15;
function step() {
setTimeout(function() {
requestAnimationFrame(step);
// Drawing code goes here
}, 1000 / fps);
}
Ваша анимация теперь контролируется браузером и вы можете указать частоту кадров до 60 кадров в секунду.
Более сложная техника заключается в проверке количества миллисекунд, прошедших с момента последнего вызова анимирующей функции и обновлять позицию элемента, полагаясь на разницу во времени. Например:
Javascript
var time;
function step() {
requestAnimationFrame(step);
var now = new Date().getTime(),
dt = now - (time || now);
time = now;
// для примера сдвиг по оси х
this.x += 10 * dt; // Увеличивать х на десять единиц в секунду
}
Удачи. Приятного «анимирования» :) :P
«Лучшим решением в случае анимации, бесспорно будет css transitions и animations. Но, к сожалению на момент написания статьи, ie9 (последняя версия ослика) ничего о css анимации не знает, да и в некоторых случаях (анимация на canvas например), css никак не поможет»
http://caniuse.com/#search=requestAnimationFrame похоже и это решение не подходит для ИЕ9)
Очень толково, спасибо!
Владимир, судя по коду для IE9 он эмулирует данную функцию.
все верно для IE9 Он эмулирует
http://caniuse.com/requestanimationframe
а вот начиная с IE10 уже можно наитивно воспользоваться
var fps = 15;
function step() {
setTimeout(function() {
requestAnimationFrame(step);
// Drawing code goes here
}, 1000 / fps);
}
Я правильно понимаю, что вызывается функция step, которая вызывает таймаут, потом по таймауту системе сообщают, что при первой возможности вызови функцию step для «лучшей» отрисовки, а сами начинаем тут же в таймауте рисовать. Это же не правильно. По идее мы должны рисовать сразу в функции step, а таймаутом дать отсрочку вызова отрисовки слеющего шага. Типа так:
var fps = 15;
function step() {
// Drawing code goes here
setTimeout(function() {
requestAnimationFrame(step);
}, 1000 / fps);
}
Мда… на предпросмотре код смотрелся нормально :(
Здравствуйте!
Не могу не где найти ответ на вопрос, как быть с анимацией css и скриптами js на мобильных ОС, где нет курсора.
В частности у меня на сайте, реализовано всплывающее меню при наведении курсора на объект(картинка+текст=ссылка страницу).
Подскажите пожалуйста, как реализовать?
Заранее спасибо за ответ!