¿Cómo animaría una línea usando el lienzo de JavaScript y TimelineLite.js?

Mira esto en Editar violín – JSFiddle:

var canvas = document.getElementById('mycanvas'),
ctx = canvas.getContext('2d'),
scene = [],
lastTime = 0, // Last seen timestamp.
drawFrame = function (timestamp) {
// Clear canvas. An efficient implementation
// will only clear as much as needed.
ctx.clearRect(0, 0, canvas.width, canvas.height); // Call update on all scene elements...
scene.forEach(function (elem) {
elem.update(timestamp - lastTime);
}); // ...then draw them.
scene.forEach(function (elem) {
elem.draw(ctx);
}); // Request to be called again.
lastTime = timestamp;
window.requestAnimationFrame(drawFrame);
}; // Kick off animation loop.
window.requestAnimationFrame(drawFrame); // Animated circle constructor.
var animatedCircle = function (starx, starty, color) {
var dir = 1, // movement direction
x = starx || canvas.width / 2,
y = starty || canvas.height / 2,
r = 50,
v = 0.1,
color = color || 'black'; return {
x: x, y: y, r: r, v: v, color: color,
update: function (deltaT) {
// Update location based on elapsed time.
this.x += this.v * deltaT * dir; // Clamp.
if (this.x + this.r > canvas.width) {
dir = -1;
}
else if (this.x - this.r < 0) {
dir = 1;
}
},
draw: function (ctx) {
// Draw element according to current property values.
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
ctx.fillStyle = this.color;
ctx.fill();
}
}
}; // Compose scene.
scene.push(animatedCircle(canvas.width * 0.33, null, 'red'));
scene.push(animatedCircle(null, canvas.height * 0.25, 'green'));
scene.push(animatedCircle(canvas.width * 0.66, canvas.height * 0.75, 'blue'));

var canvas = document.getElementById('mycanvas'),
ctx = canvas.getContext('2d'),
scene = [],
lastTime = 0, // Last seen timestamp.
drawFrame = function (timestamp) {
// Clear canvas. An efficient implementation
// will only clear as much as needed.
ctx.clearRect(0, 0, canvas.width, canvas.height); // Call update on all scene elements...
scene.forEach(function (elem) {
elem.update(timestamp - lastTime);
}); // ...then draw them.
scene.forEach(function (elem) {
elem.draw(ctx);
}); // Request to be called again.
lastTime = timestamp;
window.requestAnimationFrame(drawFrame);
}; // Kick off animation loop.
window.requestAnimationFrame(drawFrame); // Animated circle constructor.
var animatedCircle = function (starx, starty, color) {
var dir = 1, // movement direction
x = starx || canvas.width / 2,
y = starty || canvas.height / 2,
r = 50,
v = 0.1,
color = color || 'black'; return {
x: x, y: y, r: r, v: v, color: color,
update: function (deltaT) {
// Update location based on elapsed time.
this.x += this.v * deltaT * dir; // Clamp.
if (this.x + this.r > canvas.width) {
dir = -1;
}
else if (this.x - this.r < 0) {
dir = 1;
}
},
draw: function (ctx) {
// Draw element according to current property values.
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
ctx.fillStyle = this.color;
ctx.fill();
}
}
}; // Compose scene.
scene.push(animatedCircle(canvas.width * 0.33, null, 'red'));
scene.push(animatedCircle(null, canvas.height * 0.25, 'green'));
scene.push(animatedCircle(canvas.width * 0.66, canvas.height * 0.75, 'blue'));

var canvas = document.getElementById('mycanvas'),
ctx = canvas.getContext('2d'),
scene = [],
lastTime = 0, // Last seen timestamp.
drawFrame = function (timestamp) {
// Clear canvas. An efficient implementation
// will only clear as much as needed.
ctx.clearRect(0, 0, canvas.width, canvas.height); // Call update on all scene elements...
scene.forEach(function (elem) {
elem.update(timestamp - lastTime);
}); // ...then draw them.
scene.forEach(function (elem) {
elem.draw(ctx);
}); // Request to be called again.
lastTime = timestamp;
window.requestAnimationFrame(drawFrame);
}; // Kick off animation loop.
window.requestAnimationFrame(drawFrame); // Animated circle constructor.
var animatedCircle = function (starx, starty, color) {
var dir = 1, // movement direction
x = starx || canvas.width / 2,
y = starty || canvas.height / 2,
r = 50,
v = 0.1,
color = color || 'black'; return {
x: x, y: y, r: r, v: v, color: color,
update: function (deltaT) {
// Update location based on elapsed time.
this.x += this.v * deltaT * dir; // Clamp.
if (this.x + this.r > canvas.width) {
dir = -1;
}
else if (this.x - this.r < 0) {
dir = 1;
}
},
draw: function (ctx) {
// Draw element according to current property values.
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
ctx.fillStyle = this.color;
ctx.fill();
}
}
}; // Compose scene.
scene.push(animatedCircle(canvas.width * 0.33, null, 'red'));
scene.push(animatedCircle(null, canvas.height * 0.25, 'green'));
scene.push(animatedCircle(canvas.width * 0.66, canvas.height * 0.75, 'blue'));

var canvas = document.getElementById('mycanvas'),
ctx = canvas.getContext('2d'),
scene = [],
lastTime = 0, // Last seen timestamp.
drawFrame = function (timestamp) {
// Clear canvas. An efficient implementation
// will only clear as much as needed.
ctx.clearRect(0, 0, canvas.width, canvas.height); // Call update on all scene elements...
scene.forEach(function (elem) {
elem.update(timestamp - lastTime);
}); // ...then draw them.
scene.forEach(function (elem) {
elem.draw(ctx);
}); // Request to be called again.
lastTime = timestamp;
window.requestAnimationFrame(drawFrame);
}; // Kick off animation loop.
window.requestAnimationFrame(drawFrame); // Animated circle constructor.
var animatedCircle = function (starx, starty, color) {
var dir = 1, // movement direction
x = starx || canvas.width / 2,
y = starty || canvas.height / 2,
r = 50,
v = 0.1,
color = color || 'black'; return {
x: x, y: y, r: r, v: v, color: color,
update: function (deltaT) {
// Update location based on elapsed time.
this.x += this.v * deltaT * dir; // Clamp.
if (this.x + this.r > canvas.width) {
dir = -1;
}
else if (this.x - this.r < 0) {
dir = 1;
}
},
draw: function (ctx) {
// Draw element according to current property values.
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
ctx.fillStyle = this.color;
ctx.fill();
}
}
}; // Compose scene.
scene.push(animatedCircle(canvas.width * 0.33, null, 'red'));
scene.push(animatedCircle(null, canvas.height * 0.25, 'green'));
scene.push(animatedCircle(canvas.width * 0.66, canvas.height * 0.75, 'blue'));

var canvas = document.getElementById('mycanvas'),
ctx = canvas.getContext('2d'),
scene = [],
lastTime = 0, // Last seen timestamp.
drawFrame = function (timestamp) {
// Clear canvas. An efficient implementation
// will only clear as much as needed.
ctx.clearRect(0, 0, canvas.width, canvas.height); // Call update on all scene elements...
scene.forEach(function (elem) {
elem.update(timestamp - lastTime);
}); // ...then draw them.
scene.forEach(function (elem) {
elem.draw(ctx);
}); // Request to be called again.
lastTime = timestamp;
window.requestAnimationFrame(drawFrame);
}; // Kick off animation loop.
window.requestAnimationFrame(drawFrame); // Animated circle constructor.
var animatedCircle = function (starx, starty, color) {
var dir = 1, // movement direction
x = starx || canvas.width / 2,
y = starty || canvas.height / 2,
r = 50,
v = 0.1,
color = color || 'black'; return {
x: x, y: y, r: r, v: v, color: color,
update: function (deltaT) {
// Update location based on elapsed time.
this.x += this.v * deltaT * dir; // Clamp.
if (this.x + this.r > canvas.width) {
dir = -1;
}
else if (this.x - this.r < 0) {
dir = 1;
}
},
draw: function (ctx) {
// Draw element according to current property values.
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
ctx.fillStyle = this.color;
ctx.fill();
}
}
}; // Compose scene.
scene.push(animatedCircle(canvas.width * 0.33, null, 'red'));
scene.push(animatedCircle(null, canvas.height * 0.25, 'green'));
scene.push(animatedCircle(canvas.width * 0.66, canvas.height * 0.75, 'blue'));

var canvas = document.getElementById('mycanvas'),
ctx = canvas.getContext('2d'),
scene = [],
lastTime = 0, // Last seen timestamp.
drawFrame = function (timestamp) {
// Clear canvas. An efficient implementation
// will only clear as much as needed.
ctx.clearRect(0, 0, canvas.width, canvas.height); // Call update on all scene elements...
scene.forEach(function (elem) {
elem.update(timestamp - lastTime);
}); // ...then draw them.
scene.forEach(function (elem) {
elem.draw(ctx);
}); // Request to be called again.
lastTime = timestamp;
window.requestAnimationFrame(drawFrame);
}; // Kick off animation loop.
window.requestAnimationFrame(drawFrame); // Animated circle constructor.
var animatedCircle = function (starx, starty, color) {
var dir = 1, // movement direction
x = starx || canvas.width / 2,
y = starty || canvas.height / 2,
r = 50,
v = 0.1,
color = color || 'black'; return {
x: x, y: y, r: r, v: v, color: color,
update: function (deltaT) {
// Update location based on elapsed time.
this.x += this.v * deltaT * dir; // Clamp.
if (this.x + this.r > canvas.width) {
dir = -1;
}
else if (this.x - this.r < 0) {
dir = 1;
}
},
draw: function (ctx) {
// Draw element according to current property values.
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
ctx.fillStyle = this.color;
ctx.fill();
}
}
}; // Compose scene.
scene.push(animatedCircle(canvas.width * 0.33, null, 'red'));
scene.push(animatedCircle(null, canvas.height * 0.25, 'green'));
scene.push(animatedCircle(canvas.width * 0.66, canvas.height * 0.75, 'blue'));

var canvas = document.getElementById('mycanvas'),
ctx = canvas.getContext('2d'),
scene = [],
lastTime = 0, // Last seen timestamp.
drawFrame = function (timestamp) {
// Clear canvas. An efficient implementation
// will only clear as much as needed.
ctx.clearRect(0, 0, canvas.width, canvas.height); // Call update on all scene elements...
scene.forEach(function (elem) {
elem.update(timestamp - lastTime);
}); // ...then draw them.
scene.forEach(function (elem) {
elem.draw(ctx);
}); // Request to be called again.
lastTime = timestamp;
window.requestAnimationFrame(drawFrame);
}; // Kick off animation loop.
window.requestAnimationFrame(drawFrame); // Animated circle constructor.
var animatedCircle = function (starx, starty, color) {
var dir = 1, // movement direction
x = starx || canvas.width / 2,
y = starty || canvas.height / 2,
r = 50,
v = 0.1,
color = color || 'black'; return {
x: x, y: y, r: r, v: v, color: color,
update: function (deltaT) {
// Update location based on elapsed time.
this.x += this.v * deltaT * dir; // Clamp.
if (this.x + this.r > canvas.width) {
dir = -1;
}
else if (this.x - this.r < 0) {
dir = 1;
}
},
draw: function (ctx) {
// Draw element according to current property values.
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
ctx.fillStyle = this.color;
ctx.fill();
}
}
}; // Compose scene.
scene.push(animatedCircle(canvas.width * 0.33, null, 'red'));
scene.push(animatedCircle(null, canvas.height * 0.25, 'green'));
scene.push(animatedCircle(canvas.width * 0.66, canvas.height * 0.75, 'blue'));

var canvas = document.getElementById('mycanvas'),
ctx = canvas.getContext('2d'),
scene = [],
lastTime = 0, // Last seen timestamp.
drawFrame = function (timestamp) {
// Clear canvas. An efficient implementation
// will only clear as much as needed.
ctx.clearRect(0, 0, canvas.width, canvas.height); // Call update on all scene elements...
scene.forEach(function (elem) {
elem.update(timestamp - lastTime);
}); // ...then draw them.
scene.forEach(function (elem) {
elem.draw(ctx);
}); // Request to be called again.
lastTime = timestamp;
window.requestAnimationFrame(drawFrame);
}; // Kick off animation loop.
window.requestAnimationFrame(drawFrame); // Animated circle constructor.
var animatedCircle = function (starx, starty, color) {
var dir = 1, // movement direction
x = starx || canvas.width / 2,
y = starty || canvas.height / 2,
r = 50,
v = 0.1,
color = color || 'black'; return {
x: x, y: y, r: r, v: v, color: color,
update: function (deltaT) {
// Update location based on elapsed time.
this.x += this.v * deltaT * dir; // Clamp.
if (this.x + this.r > canvas.width) {
dir = -1;
}
else if (this.x - this.r < 0) {
dir = 1;
}
},
draw: function (ctx) {
// Draw element according to current property values.
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
ctx.fillStyle = this.color;
ctx.fill();
}
}
}; // Compose scene.
scene.push(animatedCircle(canvas.width * 0.33, null, 'red'));
scene.push(animatedCircle(null, canvas.height * 0.25, 'green'));
scene.push(animatedCircle(canvas.width * 0.66, canvas.height * 0.75, 'blue'));

var canvas = document.getElementById('mycanvas'),
ctx = canvas.getContext('2d'),
scene = [],
lastTime = 0, // Last seen timestamp.
drawFrame = function (timestamp) {
// Clear canvas. An efficient implementation
// will only clear as much as needed.
ctx.clearRect(0, 0, canvas.width, canvas.height); // Call update on all scene elements...
scene.forEach(function (elem) {
elem.update(timestamp - lastTime);
}); // ...then draw them.
scene.forEach(function (elem) {
elem.draw(ctx);
}); // Request to be called again.
lastTime = timestamp;
window.requestAnimationFrame(drawFrame);
}; // Kick off animation loop.
window.requestAnimationFrame(drawFrame); // Animated circle constructor.
var animatedCircle = function (starx, starty, color) {
var dir = 1, // movement direction
x = starx || canvas.width / 2,
y = starty || canvas.height / 2,
r = 50,
v = 0.1,
color = color || 'black'; return {
x: x, y: y, r: r, v: v, color: color,
update: function (deltaT) {
// Update location based on elapsed time.
this.x += this.v * deltaT * dir; // Clamp.
if (this.x + this.r > canvas.width) {
dir = -1;
}
else if (this.x - this.r < 0) {
dir = 1;
}
},
draw: function (ctx) {
// Draw element according to current property values.
ctx.beginPath();
ctx.arc(this.x, this.y, this.r, 0, 2 * Math.PI);
ctx.fillStyle = this.color;
ctx.fill();
}
}
}; // Compose scene.
scene.push(animatedCircle(canvas.width * 0.33, null, 'red'));
scene.push(animatedCircle(null, canvas.height * 0.25, 'green'));
scene.push(animatedCircle(canvas.width * 0.66, canvas.height * 0.75, 'blue'));

Para usted, TimelineLite proporcionará los métodos de update : puede pasarle referencias a sus objetos JS y se encargará de mover sus propiedades, sean cuales sean. Por lo tanto, solo debe ocuparse de las llamadas de extracción, e incluso puede ignorar la marca de tiempo que obtiene de requestAnimationFrame , ya que solo es necesario para los métodos de actualización. TimelineLite probablemente también use requestAnimationFrame internamente, pero esto no es un problema (puede suscribirse tantas funciones como desee que se le notifique cuando el navegador comience a pintarse).

Por supuesto, cuide los detalles, como encontrar qué requestAnimationFrame necesita llamar (prefijos de proveedor y setInterval ) y proporcionar un setInterval basado en setInterval para los navegadores que no tienen reqAnimFrame 🙂

Una última nota sobre el rendimiento: un bucle for con una longitud en caché ( for (var i = 0, len = arr.length; i < len; i++) ) es mucho más rápido que forEach , pero soy flojo.