Как работают анимации js? - PullRequest
3 голосов
/ 14 июля 2011

Я пытаюсь понять, как сделать анимацию javascript гладкой, и я читаю здесь несколько ответов, и я нашел кое-что, чего я не понимаю.

Вот ссылка на вопрос Плавная анимация javascript

В ответе с большинством голосов говорится: «Вот почему, как правило, рекомендуется основывать позицию / кадр на количестве времени, прошедшего с начала анимации (используя новый Date (). getTime ()) вместо перемещения / изменения фиксированной суммы в каждом кадре. "

Может кто-нибудь показать мне очень очень простой пример, который использует метод из этого ответа, и объяснить, как вы тогдаконтролировать скорость анимации?

Ответы [ 6 ]

3 голосов
/ 15 июля 2011

Обзор

Обычно с анимацией у вас есть три компонента

  1. Обновление
  2. Draw
  3. Таймер

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

function animate() {
    update(); // Executes all game logic and updates your world

    draw(); // Draws all of the animated elements onto your drawing context

    timer(); // Controls the timing of when animate will be called again
};

animate(); // Start animating

Цикл анимации - это основной контроллер потока, который происходит внутри вашей анимации. В основном код внутри цикла анимации вызывается снова и снова. Каждое выполнение функции анимации составляет кадр. Во время кадра ваш мир обновляется и перерисовывается на экране. Частота, с которой запускается функция анимации, называется частотой кадров и контролируется таймером.

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

// Create a variable with a reference to our drawing context
// Note this is not the same as using a canvas element
var canvas = document.getElementById("canvas");

Обновление

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

Массив наших автомобилей и код, используемый для генерации автомобиля, могут выглядеть следующим образом:

// A method to create new cars
var Car = new Car(x, y, vx, vy) {
    this.className = "car"; // The CSS class name we want to give the car
    this.x = x || 0; // The x position of the car
    this.y = y || 0; // The y position of the car
    this.vx = vx || 0; // the x component of the car's velocity
    this.vy = vy || 0 // the y component of the car's velocity
};

// A function that can be called to update the position of the car on each frame
Car.prototype.drive = function () {
    this.x += this.vx;
    this.y += this.vy;

    // Return an html string that represents our car sprite, with correct x and y 
    // positions
    return "<div class='" 
            + this.className 
            + "' style='left:" 
            + this.x 
            + "px; top:"
            + this.y
            + "px;'></div>";
};

// Create a variable to hold our cars
var cars = [
    new Car(10, 10, 5, 3),
    new Car(50, 22, 1, 0),
    new Car(9, 33, 20, 10)
];

Когда мы вызываем update, мы хотим вызвать метод управления каждым автомобилем, чтобы перемещать автомобильные спрайты по экрану. Этот метод привода вернет HTML-строку, представляющую спрайт, включая его текущую позицию. Мы хотим добавить эту строку в переменную, которая может быть использована для установки внутреннего HTML-элемента div холста:

// An empty string that will be used to contain the innerHTML for our canvas
var htmlStr = "";

// Update the position of each car
function update() {
    // Clear the canvas content
    htmlStr = "";

    for (var i = 0, len = cars.length; i < len; i++) {
        // Add the car sprite to the html string to be rendered
        htmlStr += cars[i].drive();
    }
};

Draw

Когда функция обновления завершит вывод наших спрайтов, нам нужно будет нарисовать элементы на холсте. Для этого мы используем метод innerHTML переменной canvas, передавая его в htmlStr, который содержит разметку, используемую для представления всех спрайтов. Это попросит браузер проанализировать текст и выложить элементы DOM на экран:

function draw() {
    // Parse the text containing our sprites and render them on the DOM tree
    canvas.innerHTML = htmlStr;
};

Вы можете утверждать, что есть лучшие способы сделать это, и, вероятно, есть. Тем не менее, в интересах сохранения простоты, я сохранял это простым. Это также не самый эффективный метод, поскольку DOM API очень медленный. Если вы посмотрите на использование системных ресурсов этого приложения, большая часть его будет перегружена вызовами innerHTML. Если вы хотите более быстрый контекст для рисования, вы должны использовать HTML 5 canvas.

Таймер

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

setTimeout(animate, 0);

Это дает указание браузерам вызывать animate после задержки 0 мс. На практике анимация никогда не будет выполняться после задержки 0 мс. Минимальное разрешение setTimeout в большинстве браузеров составляет около 15 мс, но даже это не гарантируется из-за способа работы потока пользовательского интерфейса в javascript.

Лучшее решение - использовать requestAnimationFrame, который в основном сообщает браузеру, что вы делаете анимацию. Браузер сделает кучу приятных оптимизаций для вас. Однако это не полностью поддерживается во всех браузерах. Вам следует использовать это решение для кроссбраузерного запроса Polyfill AnimationFrame:

http://paulirish.com/2011/requestanimationframe-for-smart-animating/

Тогда вы можете использовать:

requestAnimFrame(animate);

В итоге ваша законченная программа будет выглядеть примерно так:

// shim layer with setTimeout fallback
window.requestAnimFrame = (function(){
  return  window.requestAnimationFrame       || 
          window.webkitRequestAnimationFrame || 
          window.mozRequestAnimationFrame    || 
          window.oRequestAnimationFrame      || 
          window.msRequestAnimationFrame     || 
          function(/* function */ callback, /* DOMElement */ element){
            window.setTimeout(callback, 1000 / 60);
          };
})();

// Create a variable with a reference to our drawing context
// Note this is not the same as using a canvas element
var canvas = document.getElementById("canvas");

// A method to create new cars
var Car = new Car(x, y, vx, vy) {
    this.className = "car"; // The CSS class name we want to give the car
    this.x = x || 0; // The x position of the car
    this.y = y || 0; // The y position of the car
    this.vx = vx || 0; // the x component of the car's velocity
    this.vy = vy || 0 // the y component of the car's velocity
};

// A function that can be called to update the position of the car on each frame
Car.prototype.drive = function () {
    this.x += this.vx;
    this.y += this.vy;

    // Return an html string that represents our car sprite, with correct x and y positions
    return "<div class='" 
            + this.className 
            + "' style='left:" 
            + this.x 
            + "px; top:"
            + this.y
            + "px;'></div>";
};

// Create a variable to hold our cars
var cars = [
    new Car(10, 10, 5, 3),
    new Car(50, 22, 1, 0),
    new Car(9, 33, 20, 10)
];

// An empty string that will be used to contain the innerHTML for our canvas
var htmlStr = "";

// Update the position of each car
function update() {
    // Clear the canvas content
    htmlStr = "";

    for (var i = 0, len = cars.length; i < len; i++) {
        // Add the car sprite to the html string to be rendered
        htmlStr += cars[i].drive();
    }
};

function draw() {
    // Parse the text containing our sprites and render them on the DOM tree
    canvas.innerHTML = htmlStr;
};

function animate() {
    update(); // Executes all game logic and updates your world

    draw(); // Draws all of the animated elements onto your drawing context

    requestAnimFrame(animate); // Controls the timing of when animate will be called again
};

animate(); // Start animating

Заключение

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

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

0 голосов
/ 14 июля 2011

Основная идея заключается в следующем:

  • Вы хотите переместить DIV с 0,0 до 100,0 за 1 секунду.
  • Вы бы хотели, чтобы это было50 кадров в секунду (используя setTimeout (fun, 20))
  • Однако, поскольку обратный вызов не гарантированно будет выполняться ровно через 20 мсек, ваш обратный вызов должен выяснить, когда он действительно был запущен.Например, допустим, ваша анимация началась в момент X, но ваш первый обратный вызов анимации не вызывался до X + 5 мс.Вам нужно вычислить позицию, в которой div должен находиться на 25 мс анимации, ваш обратный вызов анимации не может содержать даже 50 шагов.

Вот очень простой пример кода.Я обычно не пишу с глобальными кодами, но это самый простой способ показать технику.

http://jsfiddle.net/MWWm6/2/

var duration = 1000; // in ms
var startTime; // in ms
var startX = 0;
var endX = 500;
// 0 means try to get as many frames as possible
// > 1: Remember that this is not guaranteed to run as often as requested
var refreshInterval = 0;
var div = document.getElementById('anim');

function updatePosition() {
    var now = (new Date()).getTime();
    var msSinceStart = now - startTime;
    var percentageOfProgress = msSinceStart / duration;
    var newX = (endX - startX) * percentageOfProgress;
    div.style.left = Math.min(newX, endX) + "px";
    if (window.console) {
        console.log('Animation Frame - percentageOfProgress: ' + percentageOfProgress + ' newX = ' + newX);
    }
    if (newX < endX) {
        scheduleRepaint();
    }
}

function scheduleRepaint() {
    setTimeout(updatePosition, refreshInterval);
}

div.onclick = function() {
    startTime = (new Date()).getTime();
    scheduleRepaint();
}
0 голосов
/ 14 июля 2011

Посмотрите на это так: у вас есть объект, который вы хотите переместить.Допустим, вы даете 5 секунд, чтобы перейти от A к B, и вы хотите, чтобы это было сделано со скоростью 30 кадров в секунду.Это означает, что вы должны обновить позицию объекта 150 раз и иметь максимум 1/30 = 0,0333 секунды на обновление.

Если вы используете «время с момента последней корректировки», и ваш код занимает0,05 секунды для выполнения, тогда вы не собираетесь делать 30 кадров в секунду.Однако, если вы отключаете анимацию в начале, то не имеет значения, сколько времени занимает ваш код - когда код обновления запускается, он вычислит и установит положение объекта на этой стадии животного,независимо от того, сколько кадров действительно было отображено.

0 голосов
/ 14 июля 2011

Я написал пост в блоге, который демонстрирует концепцию «основанной на времени» анимации с использованием canvas и листа спрайта.

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

http://codeutopia.net/blog/2009/08/21/using-canvas-to-do-bitmap-sprite-animation-in-javascript/

(я всегда чувствую себя таким спамом, оставляя ответы, которые в основном просто ссылаются на мой блог, даже если ссылкана самом деле совершенно актуально: D)

0 голосов
/ 14 июля 2011

Быстрый ответ заключается в том, что setTimeout не гарантирует, что обратный вызов будет выполнен n через миллисекунды после его вызова.Это просто гарантирует, что оно будет выполнено не раньше, чем n миллисекунд с того времени.Джон Резиг рассказывает об этом в этой статье .

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

0 голосов
/ 14 июля 2011

Этот пост описывает лучший способ сделать анимацию: http://paulirish.com/2011/requestanimationframe-for-smart-animating/

Это будет работать на скорости 60 кадров в секунду, или как можно быстрее.

Как только вы это узнаете, вы можете решить, сколько времени займет анимация и как далеко перемещается объект, а затем определите, как далеко он должен перемещать каждый кадр.

Конечно, для плавной анимации вы должны по возможности использовать CSS-переходы - взгляните на http://css3.bradshawenterprises.com.

...