Canvas动画
在 canvas 上绘制内容是用 canvas 提供的或者自定义的方法,而通常我们仅仅在脚本执行结束后才能看见结果,所以想在 for 循环里面完成动画是不可能的。那么为了实现动画,我们需要一些可以定时执行重绘的方法。
setInterval(function, delay)
定时器,当设定好间隔时间后,function 会定期执行。
setTimeout(function, delay)
延时器,在设定好的时间之后执行函数
requestAnimationFrame(callback)
告诉浏览器你希望执行一个动画,并在重绘之前,请求浏览器执行一个特定的函数来更新动画。
如果不需要与用户互动,可以使用 setInterval()
方法,它可以定期执行指定的代码。
如果需要做游戏,可以使用键盘或者鼠标事件配合上 setTimeout()
方法来实现。
通过设置事件监听,可以捕捉用户的交互,并执行相应的动作。
requestAnimationFrame()
方法提供了更加平缓且有效率的方式来执行动画,当系统准备好重绘条件后才会调用绘制动画帧。一般每秒钟回调函数执行 60 次,也有可能会被降低
因为通常情况下requestAnimationFrame()
方法会遵循 W3C 的建议,浏览器中的回调函数执行次数通常与浏览器屏幕刷新次数相匹配。
还有为了提高性能和电池寿命,通常 requestAnimationFrame()
方法运行在后台标签页或者隐藏时,requestAnimationFrame()
方法会暂停调用以提升性能和电池寿命。
总结一下绘制动画的基本步骤
- 清空 canvas:除非接下来要画的内容会完全充满 canvas(例如背景图),否则需要清空所有。最简单的做法就是用 clearRect 方法。
- 保存 canvas 状态:如果要改变 canvas 状态的设置(样式,变形之类的),之后又要在每画一帧之时都是原始状态的情况时,需要先保存一下。
- 绘制动画图形(animated shapes)
- 恢复 canvas 状态:如果已经保存了 canvas 的状态,可以先恢复它,然后重绘下一帧。
const canvas = document.querySelector('.canvas')
const sun = new Image()
const moon = new Image()
const earth = new Image()
let time = +new Date()
if (canvas.getContext) {
const ctx = canvas.getContext('2d')
ctx.globalCompositeOperation = 'destination-over'
const init = () => {
sun.src = './doc/imgLib/else/sun.png'
moon.src = './doc/imgLib/else/moon.png'
earth.src = './doc/imgLib/else/earth.png'
window.requestAnimationFrame(draw)
}
const draw = () => {
ctx.clearRect(0, 0, 500, 500)
ctx.fillStyle = "rgba(3, 3, 3, 0.1)"
ctx.strokeStyle = "rgba(0, 0, 0, 0.1)"
ctx.save()
ctx.save()
ctx.save()
const nowTime = +new Date()
const decSec = nowTime - time
ctx.beginPath()
ctx.translate(250, 250)
ctx.rotate(Math.PI * 2 / 6000 * decSec)
ctx.translate(200, 0)
ctx.drawImage(earth, 20 , 20, 300, 300, -30, -30, 60, 60)
// ctx.restore()
ctx.beginPath()
// ctx.translate(250, 250)
ctx.rotate(Math.PI * 2 / 2000 * decSec)
ctx.translate(60, 0)
ctx.drawImage(moon, 20 , 20, 300, 300, -20, -20, 40, 40)
ctx.restore()
ctx.translate(250, 250)
ctx.beginPath()
ctx.arc(0, 0, 200, 0, Math.PI * 2, false);
ctx.drawImage(sun, 20 , 20, 300, 300, -40, -40, 80, 80)
ctx.stroke()
ctx.restore()
window.requestAnimationFrame(draw)
}
init()
}
高级动画
在初级动画的基础上加上一些符合物理的运动,这样就能使动画更生动
const canvas = document.querySelector('.canvas')
if (canvas.getContext) {
const ctx = canvas.getContext('2d')
const init = () => {
window.requestAnimationFrame(draw)
}
const ball = {
x: 50,
y: 50,
vx: 1,
vy: 3,
radius: 20,
speed: .25,
color: "rgba(3, 3, 3, 0.1)",
draw: function() {
ctx.beginPath()
ctx.fillStyle = this.color
ctx.arc(this.x, this.y, this.radius, 0, 2 * Math.PI, false)
ctx.closePath()
ctx.fill()
}
}
const draw = () => {
// ctx.clearRect(0, 0, 500, 500)
// 用带透明度的矩形代替清空就可以保留上一次运动的痕迹
ctx.fillStyle = 'rgba(255, 255, 255, 0.3)';
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.save()
ball.draw()
ball.y += ball.vy
ball.x += ball.vx
ball.vy = ball.vy * .99
ball.vx = ball.vx * .9995
ball.vy += ball.speed
ball.speed = ball.speed - .0002 > 0 ? ball.speed - .0002: 0
if(ball.y + ball.vy > canvas.height || ball.y + ball.vy < 0){
ball.vy = -ball.vy
}
if(ball.x + ball.vx > canvas.width || ball.x + ball.vx < 0){
ball.vx = -ball.vx
}
ctx.restore()
window.requestAnimationFrame(draw)
}
init()
}