介绍
由于我们是用 JavaScript 去操控 对象,这样要实现一些交互动画也是相当容易的。
可能最大的限制就是图像一旦绘制出来,它就是一直保持那样了。如果需要移动它,我们不得不对所有东西(包括之前的)进行重绘。重绘是相当费时的,而且性能很依赖于电脑的速度。
动画基本步骤
- 清空 canvas 除非接下来要画的内容会完全充满 canvas(例如背景图),否则你需要清空所有。最简单的做法就是用 clearRect 方法。
- 保存 canvas 状态 如果你要改变一些会改变 canvas 状态的设置(样式,变形之类的),又要在每画一帧之时都是原始状态的话,你需要先保存一下。
- 绘制动画图形(animated shapes) 这一步才是重绘动画帧。
- 恢复 canvas 状态 如果已经保存了 canvas 的状态,可以先恢复它,然后重绘下一帧
更新画布
-
setInterval(function, delay)(不推荐,不准确,宏任务)
当设定好间隔时间后,function 会定期执行。 -
setTimeout(function, delay)(不推荐,不准确,宏任务)
在设定好的时间之后执行函数 -
requestAnimationFrame(callback)
告诉浏览器你希望执行一个动画,并在重绘之前,请求浏览器执行一个特定的函数来更新动画。
一般每秒钟回调函数执行 60 次
太阳系动画
<script>
window.onload= function() {
const canvasEl = document.getElementById('myCanvas')
if (!canvasEl.getContext) {
return
}
const ctx = canvasEl.getContext('2d')
let sun = new Image()
sun.src = '../images/canvas_sun.png'
let earth = new Image()
earth.src = '../images/canvas_earth.png'
let moon = new Image()
moon.src = '../images/canvas_moon.png'
requestAnimationFrame(draw)
// 一秒钟大概重绘60次
function draw() {
ctx.clearRect(0,0,300,300)
ctx.save()
// 绘制背景
drawSun()
// 绘制地球
drawEarth()
ctx.restore()
requestAnimationFrame(draw)
}
function drawSun() {
ctx.save()
ctx.drawImage(sun,0,0)
// 绘制轨道线
ctx.translate(150,150)
ctx.strokeStyle = 'rgba(255,255,255,0.3)'
ctx.beginPath()
ctx.arc(0,0,100,0,Math.PI * 2)
ctx.stroke()
ctx.restore()
}
function drawEarth() {
ctx.save()
ctx.translate(150,150)
// 地球旋转(一分钟一周)
// 获取当前时间miao
let Seconds = new Date().getSeconds()
let milliseconds = new Date().getMilliseconds()
// 一周Math.PI * 2
// 180 Math.PI * 1
// 一秒:Math.PI * 2 / 60
// 一毫秒:Math.PI * 2 / 60 / 1000
ctx.rotate(Math.PI * 2 / 60 * Seconds + Math.PI * 2 / 60 / 1000 * milliseconds)
ctx.translate(100,0)
ctx.drawImage(earth,-12,-12)
// 绘制月球
drawMoon(Seconds,milliseconds)
// 绘制蒙版
drawMengban()
ctx.restore()
}
function drawMoon(Seconds,milliseconds) {
ctx.save()
// 月球旋转(10s一周)
// 一秒: Math.PI * 2 / 10
//一毫秒:Math.PI * 2 / 10 / 1000
ctx.rotate(Math.PI * 2 / 10 * Seconds + Math.PI * 2 / 10 / 1000 * milliseconds)
ctx.translate(30,0)
ctx.drawImage(moon,-3.5,-3.5)
ctx.restore()
}
function drawMengban() {
ctx.save()
ctx.fillStyle = 'rgba(0,0,0,0.3)'
ctx.fillRect(0,-15,40,30)
ctx.restore()
}
}
</script>
时钟动画
<body>
<div class="clock">
<canvas id="tutorial" width="300" height="300px">
你的浏览器不兼容Canvas,请升级您的浏览器!
</canvas>
</div>
<script>
window.onload = function() {
let canvasEl = document.getElementById('tutorial')
if(!canvasEl.getContext){
return
}
let ctx = canvasEl.getContext('2d') // 2d | webgl
requestAnimationFrame(draw)
/**
1秒钟会回调 61次
*/
function draw() {
ctx.clearRect(0, 0, 300, 300)
ctx.save()
let time = new Date()
let hours = time.getHours()
let minute = time.getMinutes()
let second = time.getSeconds()
// 1.绘制背景(白色圆)
drawBg()
// 2.绘制的数字
drawNumbers()
// 3.绘制时针
drawHours(hours, minute, second)
// 3.绘制分针
drawMinute(minute, second)
// 4.绘制秒针
drawSecond(second)
// 5.绘制圆心
drawCircle()
// 6.画圆上的时针的刻度
drawHoursTick()
// 7.画圆上的分针的刻度
drawMinuteTick()
ctx.restore()
requestAnimationFrame(draw)
}
function drawBg(){
ctx.save()
ctx.translate(150, 150)
ctx.fillStyle ='white'
ctx.beginPath()
ctx.arc(0,0, 130, 0, Math.PI * 2)
ctx.fill()
ctx.restore()
}
function drawNumbers(){
ctx.save()
ctx.translate(150, 150)
// 开始画 3 数字
ctx.font = "30px fangsong"
ctx.textBaseline = 'middle'
ctx.textAlign = 'center'
let numbers = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 1, 2]
for(let i =0; i<numbers.length; i++){
// i = 3 , 0
// i = 4 , 占 1份, Math.PI * 2 / 12 * i
let x = Math.cos(Math.PI * 2 / 12 * i) * 100
let y = Math.sin(Math.PI * 2 / 12 * i) * 100
ctx.fillText(numbers[i], x, y)
}
ctx.restore()
}
function drawHours(hours, minute, second) {
ctx.save()
ctx.translate(150, 150) // 坐标轴原点在园的中心
// Math.PI * 2 一整个圆
// Math.PI * 2 / 12 1h
// Math.PI * 2 / 12 / 60 1min
// Math.PI * 2 / 12 / 60 / 60 1secon
// 1h + 1min + 1 second = 弧度?
ctx.rotate(
Math.PI * 2 / 12 * hours +
Math.PI * 2 / 12 / 60 * minute +
Math.PI * 2 / 12 / 60 / 60 * second
)
ctx.lineWidth = 5
ctx.lineCap = 'round'
ctx.beginPath()
ctx.moveTo(0,0)
ctx.lineTo(0, -50)
ctx.stroke()
ctx.restore()
}
function drawMinute(minute, second) {
ctx.save()
ctx.translate(150, 150) // 坐标轴原点在园的中心
// Math.PI * 2 一整个圆
// Math.PI * 2 / 60 1min
// Math.PI * 2 / 60 / 60 1sec
// 59min + 59 second = 弧度?
ctx.rotate(
Math.PI * 2 / 60 * minute +
Math.PI * 2 / 60 / 60 * second
)
ctx.lineWidth = 3
ctx.lineCap = 'round'
ctx.beginPath()
ctx.moveTo(0,0)
ctx.lineTo(0, -70)
ctx.stroke()
ctx.restore()
}
function drawSecond(second) {
ctx.save()
ctx.translate(150, 150) // 坐标轴原点在园的中心
// Math.PI * 2 一整个圆
// Math.PI * 2 / 60 1sec
// 1 second = 弧度?
ctx.rotate(
Math.PI * 2 / 60 * second
)
ctx.strokeStyle = 'red'
ctx.lineWidth = 2
ctx.lineCap = 'round'
ctx.beginPath()
ctx.moveTo(0,0)
ctx.lineTo(0, -80)
ctx.stroke()
ctx.restore()
}
function drawCircle() {
ctx.save()
ctx.translate(150, 150)
ctx.beginPath()
ctx.arc(0, 0, 8, 0, Math.PI*2)
ctx.fill()
ctx.fillStyle = 'gray'
ctx.beginPath()
ctx.arc(0, 0, 5, 0, Math.PI*2)
ctx.fill()
ctx.restore()
}
function drawHoursTick() {
ctx.save()
ctx.translate(150, 150)
for(let j = 0; j< 12 ; j ++){
ctx.rotate(Math.PI * 2 / 12 )
ctx.lineWidth = 3
ctx.beginPath()
ctx.moveTo(0, -130)
ctx.lineTo(0, -122)
ctx.stroke()
}
ctx.restore()
}
function drawMinuteTick() {
ctx.save()
ctx.translate(150, 150)
for(let j = 0; j< 60 ; j ++){
ctx.rotate(Math.PI * 2 / 60 )
ctx.lineWidth = 1
ctx.beginPath()
ctx.moveTo(0, -130)
ctx.lineTo(0, -125)
ctx.stroke()
}
ctx.restore()
}
}
</script>
</body>
参考
链接: mdn