サーチ…
2DコンテキストとrequestAnimationFrameによるシンプルなアニメーション
この例では、キャンバスと2Dコンテキストを使用して簡単なアニメーションを作成する方法を示します。キャンバスを作成してDOMに追加してコンテキストを取得する方法を知っていることが前提です
// 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
この例の jsfiddleでのデモ
指定した間隔でアニメーションを適用する(1秒ごとに新しい矩形を追加する)
この例では、キャンバスに新しい矩形を1秒ごとに追加します(== 1秒間隔)
注釈付きコード:
<!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>
指定された時間にアニメートする(アニメ時計)
この例では、秒を塗りつぶしたウェッジとして表示する時計をアニメーション表示します
注釈付きコード:
<!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>
requestAnimationFrame()を使用してアニメーションループにsetInterval()を使用しない
requestAnimationFrame
はsetIntervalと似ていますが、重要な改善点があります:
アニメーションコードは効率のために表示のリフレッシュと同期しています。クリア+再描画コードはスケジュールされていますが、直ちに実行されるわけではありません。ブラウザは、ディスプレイがリフレッシュする準備ができている場合にのみ、クリア+リドローコードを実行します。このリフレッシュサイクルとの同期により、コードの完了に最も時間がかかり、アニメーションのパフォーマンスが向上します。
すべてのループは、別のループを開始する前に常に完了します。これにより、ユーザが図面の不完全なバージョンを見る「引き裂き」が防止されます。目は特に裂傷に気付き、裂傷が発生したときに気を散らす。そのため、引き裂きを防止することで、アニメーションのスムーズさと一貫性が向上します。
- ユーザーが別のブラウザタブに切り替えると、アニメーションは自動的に停止します。これは、デバイスが現在ユーザが見ることができないアニメーションを電力消費を浪費していないため、モバイルデバイス上の電力を節約する。
デバイスの表示は毎秒約60回リフレッシュされるため、requestAnimationFrameは毎秒約60フレームで連続的に再描画できます。 eyeは1秒間に20〜30フレームの動きを見るので、requestAnimationFrameは動きの錯覚を簡単に作り出すことができます。
requestAnimationFrameは各animateCircleの最後に呼び出されます。これは、それぞれの 'requestAnimatonFrameonly'がアニメーション関数の1回の実行を要求するためです。
例:単純な `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>
requestAnimationFrameの利点を説明するために、このstackoverflowの質問にはライブデモがあります
Canvas全体に画像をアニメーション化する
この例では、キャンバス全体を読み込んでアニメーション化してイメージ化します
重要なヒント! image.onload
を使用して、イメージが完全に読み込まれるまでの時間を与えてください。
注釈付きコード
<!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>
イベントハンドラにアニメーションを描画しない(単純なスケッチアプリ)
mousemove
中には、毎秒30回のマウスイベントが氾濫します。 1秒間に30回描画を再描画できない場合があります。可能であれば、ブラウザが描画準備ができていないときに描画することで、おそらく計算能力を浪費しているでしょう(ディスプレイリフレッシュサイクルで無駄に==)。
したがって、ユーザーの入力イベント(mousemoveなど)をアニメーションの描画と区別することは理にかなっています。
イベントハンドラでは、キャンバスに描画が配置されている場所を制御するすべてのイベント変数を保存します。実際には何も描かないでください。
requestAnimationFrame
ループで、保存された情報を使用してすべての図面をCanvasにレンダリングします。
イベントハンドラを描画しないことで、キャンバスが複雑な描画をマウスイベントスピードで更新しようとすることはありません。
requestAnimationFrame
すべての描画を行うことで、ここで説明するすべての利点を得ることができます 。 アニメーションループには 'requestInimationFrame'ではなく 'setInterval'を使用します 。
注釈付きコード:
<!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>
ロバートペネナーの方程式を使って楽にする
イージングは、ある変数がある期間にわたって不均等に変化する原因となります 。
「変数」は数値で表現できる必要があり、さまざまなものを表現することができます。
- X座標、
- 矩形の幅、
- 回転角度、
- R、G、Bカラーの赤色成分。
- 数字で表現できるもの
「期間」は数字で表すことができなければならず、またさまざまなことがあります。
- ある期間、
- 移動する距離、
- 実行されるべきアニメーションループの量、
- 次のように表現できるもの
「不均等」とは、変数が最初から最後まで不均等に進んでいくことを意味します。
- 最初は速く、終わりには遅くなります。逆に、
- 期間が終了するとエンディングをオーバーシュートしますが、エンディングは終了します。
- その期間中、繰り返し弾性的に進退し、
- 期間が終了するのを待っている間、終わりから「跳ね返る」。
アトリビューション:Robert Pennerは、イージング関数の「ゴールドスタンダード」を作成しました。
引用: 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;
},
};
使用例:
// 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);
}
}
requestAnimationFrameを使用してフレームレートを設定する
requestAnimationFrameを使用すると、システムによっては60fpsより多くのフレーム/秒で更新される場合があります。レンダリングを続けることができる場合、60fpsがデフォルトレートです。システムによっては120fpsで動作するものもあります。
次の方法を使用する場合は、 (60 / FRAMES_PER_SECOND) % 1 === 0
がtrue
か、またはフレームレートが一貫しないように60の整数除算であるフレームレートのみを使用してください。
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
[x0、y0]から[x1、y1]にアニメートする
[startX、startY]から[endX、endY]までの増分[x、y]を計算するためにベクトルを使用する
// 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;
サンプルコード:
// 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);
}