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