Recherche…
Animation simple avec contexte 2D et requestAnimationFrame
Cet exemple vous montre comment créer une animation simple en utilisant le canevas et le contexte 2D. On suppose que vous savez créer et ajouter un canevas au DOM et obtenir le contexte
// 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
Une démo de cet exemple à jsfiddle
Animer à un intervalle spécifié (ajouter un nouveau rectangle toutes les 1 secondes)
Cet exemple ajoute un nouveau rectangle au canevas toutes les 1 secondes (== un intervalle de 1 seconde)
Code annoté:
<!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>
Animer à une heure spécifiée (une horloge animée)
Cet exemple anime une horloge indiquant les secondes comme un coin rempli
Code annoté:
<!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>
Utilisez requestAnimationFrame () NOT setInterval () pour les boucles d'animation
requestAnimationFrame
est similaire à setInterval, mais a ces améliorations importantes:
Le code d'animation est synchronisé avec les rafraîchissements de l'affichage pour des raisons d'efficacité Le code d'effacement + redessiner est planifié, mais pas exécuté immédiatement. Le navigateur exécute le code clair + redessiner uniquement lorsque l'affichage est prêt à être actualisé. Cette synchronisation avec le cycle de rafraîchissement augmente les performances de votre animation en donnant à votre code le temps le plus disponible.
Chaque boucle est toujours terminée avant qu'une autre boucle ne soit autorisée à démarrer. Cela évite la "déchirure", où l'utilisateur voit une version incomplète du dessin. L'œil remarque particulièrement la déchirure et est distrait lorsque la déchirure se produit. Ainsi, empêcher les déchirures rend votre animation plus fluide et plus cohérente.
- L'animation s'arrête automatiquement lorsque l'utilisateur bascule sur un autre onglet du navigateur. Cela permet d'économiser de l'énergie sur les appareils mobiles, car l'appareil ne gaspille pas de puissance en calculant une animation que l'utilisateur ne peut actuellement pas voir.
Les affichages de périphérique seront actualisés environ 60 fois par seconde, donc requestAnimationFrame peut être redessiné en continu à environ 60 "images" par seconde. L'œil voit le mouvement à 20-30 images par seconde, donc requestAnimationFrame peut facilement créer l'illusion du mouvement.
Notez que requestAnimationFrame est rappelé à la fin de chaque animateCircle. C'est parce que chaque requestAnimatonFrameonly demande une seule exécution de la fonction d'animation.
Exemple: 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>
Pour illustrer les avantages de requestAnimationFrame, cette question stackoverflow a une démonstration en direct
Animer une image sur le canevas
Cet exemple charge et anime l'image sur le canevas
Conseil important! Assurez-vous de donner à votre image le temps image.onload
pour charger complètement en utilisant image.onload
.
Code annoté
<!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>
Ne dessinez pas d'animations dans vos gestionnaires d'événements (une simple application de croquis)
Au cours de mousemove
vous recevez 30 événements de souris par seconde. Vous ne pourrez peut-être pas redessiner vos dessins 30 fois par seconde. Même si vous le pouvez, vous gaspillez probablement votre puissance de calcul en dessinant lorsque le navigateur n'est pas prêt à dessiner (gaspillage == entre les cycles de rafraîchissement de l'affichage).
Par conséquent, il est judicieux de séparer les événements de saisie de vos utilisateurs (comme mousemove) du dessin de vos animations.
Dans les gestionnaires d'événements, enregistrez toutes les variables d'événement qui contrôlent l'emplacement des dessins sur le canevas. Mais ne dessine rien.
Dans une boucle
requestAnimationFrame
,requestAnimationFrame
tous les dessins dans le canevas à l'aide des informations enregistrées.
En ne dessinant pas dans les gestionnaires d'événements, vous n'obligez pas Canvas à essayer d'actualiser des dessins complexes à la vitesse des événements de la souris.
En effectuant tous les dessins dans requestAnimationFrame
vous requestAnimationFrame
tous les avantages décrits ici. Utilisez 'requestanimationFrame' et non 'setInterval' pour les boucles d'animation .
Code annoté:
<!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>
Faciliter l'utilisation des équations de Robert Penners
Un assouplissement fait que certaines variables changent de manière inégale sur une durée .
"variable" doit pouvoir être exprimé sous forme de nombre et peut représenter une variété remarquable de choses:
- une coordonnée X,
- la largeur d'un rectangle,
- un angle de rotation,
- la composante rouge d'une couleur R, G, B.
- tout ce qui peut être exprimé sous forme de nombre.
La "durée" doit pouvoir être exprimée sous forme de nombre et peut aussi être une variété de choses:
- une période de temps,
- une distance à parcourir,
- une quantité de boucles d'animation à exécuter,
- tout ce qui peut être exprimé comme
"inégalement" signifie que la variable progresse de façon inégale vers les valeurs finales:
- plus vite au début et plus lent à la fin - ou vice-versa,
- dépasse la fin mais revient à la fin à la fin de la durée,
- avance / recule élastiquement à plusieurs reprises pendant la durée,
- "rebondit" sur la fin tout en se reposant à la fin de la durée.
Attribution: Robert Penner a créé le "standard de référence" des fonctions d'assouplissement.
Citer: 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;
},
};
Exemple d'utilisation:
// 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);
}
}
Définir la fréquence d'images à l'aide de requestAnimationFrame
Utiliser requestAnimationFrame peut sur certains systèmes mettre à jour plus d'images par seconde que les 60fps. 60fps est le taux par défaut si le rendu peut suivre. Certains systèmes fonctionneront à 120 images par seconde, voire plus.
Si vous utilisez la méthode suivante, vous ne devez utiliser que des cadences qui sont des divisions entières de 60, de sorte que (60 / FRAMES_PER_SECOND) % 1 === 0
soit true
ou que vous obtiendrez des fréquences d'images incohérentes.
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
Animer de [x0, y0] à [x1, y1]
Utiliser des vecteurs pour calculer les incréments [x, y] de [startX, startY] à [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;
Exemple de code:
// 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);
}