要在HTML5 canvas中绘制图像动画效果,你需要绘制出每一帧的图像,然后在一个极短的时间内从一帧过渡到下一帧,形成动画效果。这其实是一种视觉欺骗,原理就像播放电影一样,胶片上每一格是一帧图片,然后快速的播放它们,在人的眼睛看来就是一个完整的动画效果。
制作canvas动画的基本步骤
下面是你在canvas上绘制一个动画帧的基本步骤:
- 1、清空canvas:除了背景图像之外,你需要清空之前绘制的所有图形。
- 2、保存canvas的状态:如果在这一步中你使用了不同的绘图状态(例如描边大小和填充色等),并且你想在绘制每一帧时使用相同的原始状态,你需要保存这些原始状态。
- 3、绘制动画图形:这一步中你需要绘制那些动画的图形元素。
- 4、恢复canvas状态:如果你之前保存过canvas的状态,在这一步中将它们恢复。
控制canvas动画
我们需要一种方法来在指定时间内执行我们的绘制图形函数。有两种方式可以控制动画的执行。
第一种是使用下面的三个window
对象上的方法:window.setInterval()
,window.setTimeout()
和window.requestAnimationFrame()
。它们都能在指定时间内调用指定的函数。
setInterval(function, delay)
:在每delay
毫秒时间内反复执行function
指定的函数。setTimeout(function, delay)
:在delay
毫秒内执行function
指定的函数。requestAnimationFrame(callback)
:通知浏览器你需要执行一个动画,并请求浏览器调用指定的函数来在下一次重绘前更新动画。
第二种方法是使用事件监听。例如你需要做一个小游戏,你可以监听键盘和鼠标的事件,然后在捕获相应的事件时使用setTimeout()
方法来制作动画效果。
为了获得更好的动画性能,我们通常使用requestAnimationFrame()
方法。当浏览器装备好绘制下一帧动画时,我们可以将绘制函数作为参数传入这个方法中。
通过在浏览器准备画下一帧的时候,给浏览器发出信号,可以使浏览器对你的动画进行硬件加速,这比使用setTimeout()
来绘制动画效果会好得多。
下面是一段示例代码:
function animate() { reqAnimFrame = window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame ; reqAnimFrame(animate); draw(); }
animate()
函数首先会获取requestAnimationFrame()
函数的一个引用。注意在不同的浏览器中会使用不同的名称。变量reqAnimFrame
会在不同的浏览器中设置为不同的值,总之它不能为空。
然后reqAnimFrame()
方法被调用,并将animate()
函数作为参数传入。当浏览器准备好绘制下一帧动画时,animate()
函数就会被调用。
最后,animate()
函数会调用draw()
方法。draw()
方法在上面的代码中没有写出来,它实际上做的事情就是前面提到的绘制一个动画帧的4个步骤:清空canvas,保存状态,绘制图形,恢复状态。
还有一件需要注意的事情是animate()
函数必须被调用一次来启动动画,否则requestAnimationFrame()
函数将永远不会被调用,动画也不会被正常执行。
下面是一个小例子:一个小矩形在canvas中来回不停的运动。
上面canvas动画的实现代码如下:
var x = 0; var y = 15; var speed = 5; function animate() { reqAnimFrame = window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame ; reqAnimFrame(animate); x += speed; if(x <= 0 || x >= 475){ speed = -speed; } draw(); } function draw() { var canvas = document.getElementById("ex1"); var context = canvas.getContext("2d"); context.clearRect(0, 0, 500, 170); context.fillStyle = "#ff00ff"; context.fillRect(x, y, 25, 25); } animate();
canvas动画示例
下面是一个地球绕太阳以及月亮绕地球旋转的canvas动画效果。
上面效果的实现代码如下:
var sun = new Image(); var moon = new Image(); var earth = new Image(); function init(){ sun.src = 'img/Canvas_sun.png'; moon.src = 'img/Canvas_moon.png'; earth.src = 'img/Canvas_earth.png'; reqAnimFrame = window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame ; reqAnimFrame(drawSolarSystem); } function drawSolarSystem () { var ctx = document.getElementById('ex2').getContext('2d'); ctx.globalCompositeOperation = 'destination-over'; ctx.clearRect(0,0,300,300); // clear canvas ctx.fillStyle = 'rgba(0,0,0,0.4)'; ctx.strokeStyle = 'rgba(0,153,255,0.4)'; ctx.save(); ctx.translate(150,150); // Earth var time = new Date(); ctx.rotate( ((2*Math.PI)/60)*time.getSeconds() + ((2*Math.PI)/60000)*time.getMilliseconds() ); ctx.translate(105,0); ctx.fillRect(0,-12,50,24); // Shadow ctx.drawImage(earth,-12,-12); // Moon ctx.save(); ctx.rotate( ((2*Math.PI)/6)*time.getSeconds() + ((2*Math.PI)/6000)*time.getMilliseconds() ); ctx.translate(0,28.5); ctx.drawImage(moon,-3.5,-3.5); ctx.restore(); ctx.restore(); ctx.beginPath(); ctx.arc(150,150,105,0,Math.PI*2,false); // Earth orbit ctx.stroke(); ctx.drawImage(sun,0,0,300,300); reqAnimFrame = window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame ; reqAnimFrame(drawSolarSystem); } init();
接下来的例子是一个模拟时钟动画的例子。
模拟时钟动画的实现代码如下:
function clock(){ var now = new Date(); var ctx = document.getElementById('ex3').getContext('2d'); ctx.save(); ctx.clearRect(0,0,150,150); ctx.translate(75,75); ctx.scale(0.4,0.4); ctx.rotate(-Math.PI/2); ctx.strokeStyle = "black"; ctx.fillStyle = "white"; ctx.lineWidth = 8; ctx.lineCap = "round"; // Hour marks ctx.save(); for (var i=0;i<12;i++){ ctx.beginPath(); ctx.rotate(Math.PI/6); ctx.moveTo(100,0); ctx.lineTo(120,0); ctx.stroke(); } ctx.restore(); // Minute marks ctx.save(); ctx.lineWidth = 5; for (i=0;i<60;i++){ if (i%5!=0) { ctx.beginPath(); ctx.moveTo(117,0); ctx.lineTo(120,0); ctx.stroke(); } ctx.rotate(Math.PI/30); } ctx.restore(); var sec = now.getSeconds(); var min = now.getMinutes(); var hr = now.getHours(); hr = hr>=12 ? hr-12 : hr; ctx.fillStyle = "black"; // write Hours ctx.save(); ctx.rotate( hr*(Math.PI/6) + (Math.PI/360)*min + (Math.PI/21600)*sec ) ctx.lineWidth = 14; ctx.beginPath(); ctx.moveTo(-20,0); ctx.lineTo(80,0); ctx.stroke(); ctx.restore(); // write Minutes ctx.save(); ctx.rotate( (Math.PI/30)*min + (Math.PI/1800)*sec ) ctx.lineWidth = 10; ctx.beginPath(); ctx.moveTo(-28,0); ctx.lineTo(112,0); ctx.stroke(); ctx.restore(); // Write seconds ctx.save(); ctx.rotate(sec * Math.PI/30); ctx.strokeStyle = "#D40000"; ctx.fillStyle = "#D40000"; ctx.lineWidth = 6; ctx.beginPath(); ctx.moveTo(-30,0); ctx.lineTo(83,0); ctx.stroke(); ctx.beginPath(); ctx.arc(0,0,10,0,Math.PI*2,true); ctx.fill(); ctx.beginPath(); ctx.arc(95,0,10,0,Math.PI*2,true); ctx.stroke(); ctx.fillStyle = "rgba(0,0,0,0)"; ctx.arc(0,0,3,0,Math.PI*2,true); ctx.fill(); ctx.restore(); ctx.beginPath(); ctx.lineWidth = 14; ctx.strokeStyle = '#325FA2'; ctx.arc(0,0,142,0,Math.PI*2,true); ctx.stroke(); ctx.restore(); reqAnimFrame = window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame ; reqAnimFrame(clock); } reqAnimFrame = window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame || window.oRequestAnimationFrame ; reqAnimFrame(clock);