Buscar..
Animación simple con contexto 2D y requestAnimationFrame
Este ejemplo le mostrará cómo crear una animación simple utilizando el lienzo y el contexto 2D. Se supone que sabe cómo crear y agregar un lienzo al DOM y obtener el contexto
// this example assumes ctx and canvas have been created
const textToDisplay = "This is an example that uses the canvas to animate some text.";
const textStyle = "white";
const BGStyle = "black"; // background style
const textSpeed = 0.2; // in pixels per millisecond
const textHorMargin = 8; // have the text a little outside the canvas
ctx.font = Math.floor(canvas.height * 0.8) + "px arial"; // size the font to 80% of canvas height
var textWidth = ctx.measureText(textToDisplay).width; // get the text width
var totalTextSize = (canvas.width + textHorMargin * 2 + textWidth);
ctx.textBaseline = "middle"; // not put the text in the vertical center
ctx.textAlign = "left"; // align to the left
var textX = canvas.width + 8; // start with the text off screen to the right
var textOffset = 0; // how far the text has moved
var startTime;
// this function is call once a frame which is approx 16.66 ms (60fps)
function update(time){ // time is passed by requestAnimationFrame
if(startTime === undefined){ // get a reference for the start time if this is the first frame
startTime = time;
}
ctx.fillStyle = BGStyle;
ctx.fillRect(0, 0, canvas.width, canvas.height); // clear the canvas by drawing over it
textOffset = ((time - startTime) * textSpeed) % (totalTextSize); // move the text left
ctx.fillStyle = textStyle; // set the text style
ctx.fillText(textToDisplay, textX - textOffset, canvas.height / 2); // render the text
requestAnimationFrame(update);// all done request the next frame
}
requestAnimationFrame(update);// to start request the first frame
Una demostración de este ejemplo en jsfiddle
Animar en un intervalo específico (agregar un nuevo rectángulo cada 1 segundo)
Este ejemplo agrega un nuevo rectángulo al lienzo cada 1 segundo (== un intervalo de 1 segundo)
Código anotado:
<!doctype html>
<html>
<head>
<style>
body{ background-color:white; }
#canvas{border:1px solid red; }
</style>
<script>
window.onload=(function(){
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// animation interval variables
var nextTime=0; // the next animation begins at "nextTime"
var duration=1000; // run animation every 1000ms
var x=20; // the X where the next rect is drawn
// start the animation
requestAnimationFrame(animate);
function animate(currentTime){
// wait for nextTime to occur
if(currentTime<nextTime){
// request another loop of animation
requestAnimationFrame(animate);
// time hasn't elapsed so just return
return;
}
// set nextTime
nextTime=currentTime+duration;
// add another rectangle every 1000ms
ctx.fillStyle='#'+Math.floor(Math.random()*16777215).toString(16);
ctx.fillRect(x,30,30,30);
// update X position for next rectangle
x+=30;
// request another loop of animation
requestAnimationFrame(animate);
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=512 height=512></canvas>
</body>
</html>
Animar a una hora determinada (un reloj animado)
Este ejemplo anima un reloj que muestra los segundos como una cuña llena.
Código anotado:
<!doctype html>
<html>
<head>
<style>
body{ background-color:white; }
#canvas{border:1px solid red; }
</style>
<script>
window.onload=(function(){
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// canvas styling for the clock
ctx.strokeStyle='lightgray';
ctx.fillStyle='skyblue';
ctx.lineWidth=5;
// cache often used values
var PI=Math.PI;
var fullCircle=PI*2;
var sa=-PI/2; // == the 12 o'clock angle in context.arc
// start the animation
requestAnimationFrame(animate);
function animate(currentTime){
// get the current seconds value from the system clock
var date=new Date();
var seconds=date.getSeconds();
// clear the canvas
ctx.clearRect(0,0,cw,ch);
// draw a full circle (== the clock face);
ctx.beginPath();
ctx.moveTo(100,100);
ctx.arc(100,100,75,0,fullCircle);
ctx.stroke();
// draw a wedge representing the current seconds value
ctx.beginPath();
ctx.moveTo(100,100);
ctx.arc(100,100,75,sa,sa+fullCircle*seconds/60);
ctx.fill();
// request another loop of animation
requestAnimationFrame(animate);
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=512 height=512></canvas>
</body>
</html>
Use requestAnimationFrame () NOT setInterval () para los bucles de animación
requestAnimationFrame
es similar a setInterval, pero tiene estas importantes mejoras:
El código de animación se sincroniza con las actualizaciones de pantalla para mejorar la eficiencia. El código de borrado + redibujado está programado, pero no se ejecuta de inmediato. El navegador ejecutará el código de borrado + redibujado solo cuando la pantalla esté lista para actualizarse. Esta sincronización con el ciclo de actualización aumenta el rendimiento de su animación al darle a su código el tiempo más disponible para completar.
Cada bucle siempre se completa antes de que se pueda iniciar otro bucle. Esto evita "rasgar", donde el usuario ve una versión incompleta del dibujo. El ojo nota particularmente lagrimeo y se distrae cuando se produce lagrimeo. Por lo tanto, prevenir el desgarro hace que su animación se vea más suave y más consistente.
- La animación se detiene automáticamente cuando el usuario cambia a una pestaña diferente del navegador. Esto ahorra energía en los dispositivos móviles porque el dispositivo no desperdicia energía en una animación que el usuario no puede ver actualmente.
Las pantallas del dispositivo se actualizarán aproximadamente 60 veces por segundo, por lo que requestAnimationFrame puede volver a dibujar continuamente a aproximadamente 60 "cuadros" por segundo. El ojo ve movimiento a 20-30 cuadros por segundo, por lo que requestAnimationFrame puede crear fácilmente la ilusión de movimiento.
Observe que requestAnimationFrame se recupera al final de cada animateCircle. Esto se debe a que cada 'requestAnimatonFrameonly solicita una ejecución única de la función de animación.
Ejemplo: simple `requestAnimationFrame
<!doctype html>
<html>
<head>
<style>
body{ background-color:white; }
#canvas{border:1px solid red; }
</style>
<script>
window.onload=(function(){
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// start the animation
requestAnimationFrame(animate);
function animate(currentTime){
// draw a full randomly circle
var x=Math.random()*canvas.width;
var y=Math.random()*canvas.height;
var radius=10+Math.random()*15;
ctx.beginPath();
ctx.arc(x,y,radius,0,Math.PI*2);
ctx.fillStyle='#'+Math.floor(Math.random()*16777215).toString(16);
ctx.fill();
// request another loop of animation
requestAnimationFrame(animate);
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=512 height=512></canvas>
</body>
</html>
Para ilustrar las ventajas de requestAnimationFrame, esta pregunta de stackoverflow tiene una demostración en vivo
Animar una imagen a través del lienzo.
Este ejemplo carga y anima una imagen a través del lienzo.
Sugerencia importante! Asegúrese de darle tiempo a su imagen para que se cargue completamente usando image.onload
.
Código anotado
<!doctype html>
<html>
<head>
<style>
body{ background-color:white; }
#canvas{border:1px solid red; }
</style>
<script>
window.onload=(function(){
// canvas related variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// animation related variables
var minX=20; // Keep the image animating
var maxX=250; // between minX & maxX
var x=minX; // The current X-coordinate
var speedX=1; // The image will move at 1px per loop
var direction=1; // The image direction: 1==righward, -1==leftward
var y=20; // The Y-coordinate
// Load a new image
// IMPORTANT!!! You must give the image time to load by using img.onload!
var img=new Image();
img.onload=start;
img.src="https://dl.dropboxusercontent.com/u/139992952/stackoverflow/sun.png";
function start(){
// the image is fully loaded sostart animating
requestAnimationFrame(animate);
}
function animate(time){
// clear the canvas
ctx.clearRect(0,0,cw,ch);
// draw
ctx.drawImage(img,x,y);
// update
x += speedX * direction;
// keep "x" inside min & max
if(x<minX){ x=minX; direction*=-1; }
if(x>maxX){ x=maxX; direction*=-1; }
// request another loop of animation
requestAnimationFrame(animate);
}
}); // end $(function(){});
</script>
</head>
<body>
<canvas id="canvas" width=512 height=512></canvas>
</body>
</html>
No dibujes animaciones en tus controladores de eventos (una aplicación de boceto simple)
Durante mousemove
te mousemove
con 30 eventos de ratón por segundo. Es posible que no pueda volver a dibujar sus dibujos a 30 veces por segundo. Incluso si puede, probablemente esté desperdiciando poder de cómputo al dibujar cuando el navegador no está listo para dibujar (desperdiciado == a través de los ciclos de actualización de la pantalla).
Por lo tanto, tiene sentido separar los eventos de entrada de sus usuarios (como mousemove) del dibujo de sus animaciones.
En los controladores de eventos, guarde todas las variables de eventos que controlan dónde se ubican los dibujos en el Lienzo. Pero en realidad no dibujar nada.
En un bucle
requestAnimationFrame
, renderice todos los dibujos al lienzo utilizando la información guardada.
Al no dibujar en los controladores de eventos, no está obligando a Canvas a intentar actualizar dibujos complejos a velocidades de eventos del mouse.
Al hacer todos los dibujos en requestAnimationFrame
, obtiene todos los beneficios descritos aquí. Use 'requestanimationFrame' no 'setInterval' para los bucles de animación .
Código anotado:
<!doctype html>
<html>
<head>
<style>
body{ background-color: ivory; }
#canvas{border:1px solid red; }
</style>
<script>
window.onload=(function(){
function log(){console.log.apply(console,arguments);}
// canvas variables
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// set canvas styling
ctx.strokeStyle='skyblue';
ctx.lineJoint='round';
ctx.lineCap='round';
ctx.lineWidth=6;
// handle windows scrolling & resizing
function reOffset(){
var BB=canvas.getBoundingClientRect();
offsetX=BB.left;
offsetY=BB.top;
}
var offsetX,offsetY;
reOffset();
window.onscroll=function(e){ reOffset(); }
window.onresize=function(e){ reOffset(); }
// vars to save points created during mousemove handling
var points=[];
var lastLength=0;
// start the animation loop
requestAnimationFrame(draw);
canvas.onmousemove=function(e){handleMouseMove(e);}
function handleMouseMove(e){
// tell the browser we're handling this event
e.preventDefault();
e.stopPropagation();
// get the mouse position
mouseX=parseInt(e.clientX-offsetX);
mouseY=parseInt(e.clientY-offsetY);
// save the mouse position in the points[] array
// but don't draw anything
points.push({x:mouseX,y:mouseY});
}
function draw(){
// No additional points? Request another frame an return
var length=points.length;
if(length==lastLength){requestAnimationFrame(draw);return;}
// draw the additional points
var point=points[lastLength];
ctx.beginPath();
ctx.moveTo(point.x,point.y)
for(var i=lastLength;i<length;i++){
point=points[i];
ctx.lineTo(point.x,point.y);
}
ctx.stroke();
// request another animation loop
requestAnimationFrame(draw);
}
}); // end window.onload
</script>
</head>
<body>
<h4>Move mouse over Canvas to sketch</h4>
<canvas id="canvas" width=512 height=512></canvas>
</body>
</html>
Facilitando el uso de las ecuaciones de Robert Penners.
Una flexibilización hace que alguna variable cambie de manera desigual a lo largo de una duración .
"variable" debe poder expresarse como un número y puede representar una variedad notable de cosas:
- una coordenada X,
- el ancho de un rectángulo,
- un ángulo de rotación,
- El componente rojo de un color R, G, B.
- Cualquier cosa que se pueda expresar como un número.
La "duración" debe poder expresarse como un número y también puede ser una variedad de cosas:
- un período de tiempo,
- una distancia a recorrer,
- una cantidad de bucles de animación para ser ejecutados,
- cualquier cosa que pueda ser expresada como
"desigual" significa que la variable avanza de manera desigual desde el principio hasta el final:
- más rápido al principio y más lento al final, o viceversa,
- sobrepasa el final pero retrocede hasta el final a medida que termina la duración,
- Avanza / retrocede repetidamente elásticamente durante la duración,
- "rebota" en el final mientras se detiene a medida que la duración termina.
Atribución: Robert Penner ha creado el "estándar de oro" de las funciones de aceleración.
Cite: https://github.com/danro/jquery-easing/blob/master/jquery.easing.js
// t: elapsed time inside duration (currentTime-startTime),
// b: beginning value,
// c: total change from beginning value (endingValue-startingValue),
// d: total duration
var Easings={
easeInQuad: function (t, b, c, d) {
return c*(t/=d)*t + b;
},
easeOutQuad: function (t, b, c, d) {
return -c *(t/=d)*(t-2) + b;
},
easeInOutQuad: function (t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t + b;
return -c/2 * ((--t)*(t-2) - 1) + b;
},
easeInCubic: function (t, b, c, d) {
return c*(t/=d)*t*t + b;
},
easeOutCubic: function (t, b, c, d) {
return c*((t=t/d-1)*t*t + 1) + b;
},
easeInOutCubic: function (t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t*t + b;
return c/2*((t-=2)*t*t + 2) + b;
},
easeInQuart: function (t, b, c, d) {
return c*(t/=d)*t*t*t + b;
},
easeOutQuart: function (t, b, c, d) {
return -c * ((t=t/d-1)*t*t*t - 1) + b;
},
easeInOutQuart: function (t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
return -c/2 * ((t-=2)*t*t*t - 2) + b;
},
easeInQuint: function (t, b, c, d) {
return c*(t/=d)*t*t*t*t + b;
},
easeOutQuint: function (t, b, c, d) {
return c*((t=t/d-1)*t*t*t*t + 1) + b;
},
easeInOutQuint: function (t, b, c, d) {
if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
return c/2*((t-=2)*t*t*t*t + 2) + b;
},
easeInSine: function (t, b, c, d) {
return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
},
easeOutSine: function (t, b, c, d) {
return c * Math.sin(t/d * (Math.PI/2)) + b;
},
easeInOutSine: function (t, b, c, d) {
return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
},
easeInExpo: function (t, b, c, d) {
return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
},
easeOutExpo: function (t, b, c, d) {
return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
},
easeInOutExpo: function (t, b, c, d) {
if (t==0) return b;
if (t==d) return b+c;
if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
},
easeInCirc: function (t, b, c, d) {
return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
},
easeOutCirc: function (t, b, c, d) {
return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
},
easeInOutCirc: function (t, b, c, d) {
if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
},
easeInElastic: function (t, b, c, d) {
var s=1.70158;var p=0;var a=c;
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
if (a < Math.abs(c)) { a=c; var s=p/4; }
else var s = p/(2*Math.PI) * Math.asin (c/a);
return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
},
easeOutElastic: function (t, b, c, d) {
var s=1.70158;var p=0;var a=c;
if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
if (a < Math.abs(c)) { a=c; var s=p/4; }
else var s = p/(2*Math.PI) * Math.asin (c/a);
return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
},
easeInOutElastic: function (t, b, c, d) {
var s=1.70158;var p=0;var a=c;
if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5);
if (a < Math.abs(c)) { a=c; var s=p/4; }
else var s = p/(2*Math.PI) * Math.asin (c/a);
if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
},
easeInBack: function (t, b, c, d, s) {
if (s == undefined) s = 1.70158;
return c*(t/=d)*t*((s+1)*t - s) + b;
},
easeOutBack: function (t, b, c, d, s) {
if (s == undefined) s = 1.70158;
return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
},
easeInOutBack: function (t, b, c, d, s) {
if (s == undefined) s = 1.70158;
if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
},
easeInBounce: function (t, b, c, d) {
return c - Easings.easeOutBounce (d-t, 0, c, d) + b;
},
easeOutBounce: function (t, b, c, d) {
if ((t/=d) < (1/2.75)) {
return c*(7.5625*t*t) + b;
} else if (t < (2/2.75)) {
return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
} else if (t < (2.5/2.75)) {
return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
} else {
return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
}
},
easeInOutBounce: function (t, b, c, d) {
if (t < d/2) return Easings.easeInBounce (t*2, 0, c, d) * .5 + b;
return Easings.easeOutBounce (t*2-d, 0, c, d) * .5 + c*.5 + b;
},
};
Ejemplo de uso:
// include the Easings object from above
var Easings = ...
// Demo
var startTime;
var beginningValue=50; // beginning x-coordinate
var endingValue=450; // ending x-coordinate
var totalChange=endingValue-beginningValue;
var totalDuration=3000; // ms
var keys=Object.keys(Easings);
ctx.textBaseline='middle';
requestAnimationFrame(animate);
function animate(time){
var PI2=Math.PI*2;
if(!startTime){startTime=time;}
var elapsedTime=Math.min(time-startTime,totalDuration);
ctx.clearRect(0,0,cw,ch);
ctx.beginPath();
for(var y=0;y<keys.length;y++){
var key=keys[y];
var easing=Easings[key];
var easedX=easing(
elapsedTime,beginningValue,totalChange,totalDuration);
if(easedX>endingValue){easedX=endingValue;}
ctx.moveTo(easedX,y*15);
ctx.arc(easedX,y*15+10,5,0,PI2);
ctx.fillText(key,460,y*15+10-1);
}
ctx.fill();
if(time<startTime+totalDuration){
requestAnimationFrame(animate);
}
}
Establecer la velocidad de fotogramas utilizando requestAnimationFrame
El uso de requestAnimationFrame puede actualizarse en algunos sistemas a más cuadros por segundo que los 60 fps. 60fps es la tasa predeterminada si la representación puede continuar. Algunos sistemas se ejecutarán a 120 fps tal vez más.
Si usa el siguiente método, solo debe usar velocidades de cuadro que sean divisiones enteras de 60, de modo que (60 / FRAMES_PER_SECOND) % 1 === 0
sea true
o obtendrá velocidades de cuadro inconsistentes.
const FRAMES_PER_SECOND = 30; // Valid values are 60,30,20,15,10...
// set the mim time to render the next frame
const FRAME_MIN_TIME = (1000/60) * (60 / FRAMES_PER_SECOND) - (1000/60) * 0.5;
var lastFrameTime = 0; // the last frame time
function update(time){
if(time-lastFrameTime < FRAME_MIN_TIME){ //skip the frame if the call is too early
requestAnimationFrame(update);
return; // return as there is nothing to do
}
lastFrameTime = time; // remember the time of the rendered frame
// render the frame
requestAnimationFrame(update); // get next farme
}
requestAnimationFrame(update); // start animation
Animar de [x0, y0] a [x1, y1]
Usa vectores para calcular [x, y] incrementales de [startX, startY] a [endX, endY]
// dx is the total distance to move in the X direction
var dx = endX - startX;
// dy is the total distance to move in the Y direction
var dy = endY - startY;
// use a pct (percentage) to travel the total distances
// start at 0% which == the starting point
// end at 100% which == then ending point
var pct=0;
// use dx & dy to calculate where the current [x,y] is at a given pct
var x = startX + dx * pct/100;
var y = startY + dx * pct/100;
Código de ejemplo:
// canvas vars
var canvas=document.createElement("canvas");
document.body.appendChild(canvas);
canvas.style.border='1px solid red';
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
// canvas styles
ctx.strokeStyle='skyblue';
ctx.fillStyle='blue';
// animating vars
var pct=101;
var startX=20;
var startY=50;
var endX=225;
var endY=100;
var dx=endX-startX;
var dy=endY-startY;
// start animation loop running
requestAnimationFrame(animate);
// listen for mouse events
window.onmousedown=(function(e){handleMouseDown(e);});
window.onmouseup=(function(e){handleMouseUp(e);});
// constantly running loop
// will animate dot from startX,startY to endX,endY
function animate(time){
// demo: rerun animation
if(++pct>100){pct=0;}
// update
x=startX+dx*pct/100;
y=startY+dy*pct/100;
// draw
ctx.clearRect(0,0,cw,ch);
ctx.beginPath();
ctx.moveTo(startX,startY);
ctx.lineTo(endX,endY);
ctx.stroke();
ctx.beginPath();
ctx.arc(x,y,5,0,Math.PI*2);
ctx.fill()
// request another animation loop
requestAnimationFrame(animate);
}