canvas绘制动画原理及案例讲解(绘制小恐龙动画、时钟等) 您所在的位置:网站首页 小恐龙像素画移动素材 canvas绘制动画原理及案例讲解(绘制小恐龙动画、时钟等)

canvas绘制动画原理及案例讲解(绘制小恐龙动画、时钟等)

2024-07-09 18:24| 来源: 网络整理| 查看: 265

大家好,我是潘潘。

这期为大家带来的是canvas的动画绘制案例与讲解。不知道大家上一期canvas绘制基本图形的入门教程看的怎么样了,如果已经遗忘了或者还没看的小伙伴建议先去看一下,上一期是学习这一期的基础:

canvas详细教程!(近1万字吐血分享)

因为canvas的功能实在太强大了,为了让大家一点一点来,这里只展示了几个适合新手学习的canvas绘制动画的案例,高级动画案例会在下一期讲解。

canvas绘制动画

在绘制动画之前,我们先了解一下canvas绘制动画的基本原理和方法。

绘制原理

清屏→更新→渲染

在canvas之前,在web端绘制动画都是用Flash实现的,但是Flash漏洞很多,还必须安装插件(记不记得小时候玩一些小游戏和播放视频时提示要下载flash插件),Flash在2021年初已经被正式停用了。canvas的出现颠覆了Flash的地位,无论是广告、游戏都可以用canvas实现,Canvas是一个轻量级的画布,在使用canvas绘制的时候,一旦绘制成功,就会像素化它们,canvas没有再次从画布上得到这个图形的能力,没有能力再去修改已经画在画布上的内容,这也是canvas比较轻量的原因。所以,如果要在同一地方绘制不同的图案,就需要先清除画布的这一区域,再绘制新图案。

常用的绘制方法

canvas上绘制内容是要在js脚本执行结束之后才能看到结果,所以我们不能在for循环中完成动画的绘制,而是常用一些浏览器内置的方法:

setTimeout(code, milliseconds, param1, param2, ...); :延时器,不多讲;setInterval(function, milliseconds, param1, param2, ...); :定时器,不多讲;window.requestAnimationFrame(callback) :告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。

MDNsetTimeout/ setInterval 的显著缺陷就是设定的时间并不精确,它们只是在设定的时间后将相应任务添加到任务队列中,而任务队列中如果还有前面的任务尚未执行完毕,那么后添加的任务就必须等待,这个等待的时间造成了原本设定的动画时间间隔不准。requestAnimationFrame的到来就是解决这个问题的 ,requestAnimationFrame是浏览器用于定时循环操作的一个接口,类似于setTimeout,主要用途是按帧对网页进行重绘。 设置这个API的目的是为了让各种网页动画效果(DOM动画、Canvas动画、SVG动画、WebGL动画)能够有一个统一的刷新机制,从而节省系统资源,提高系统性能,改善视觉效果。代码中使用这个API,就是告诉浏览器希望执行一个动画,让浏览器在下一个动画帧安排一次网页重绘。 requestAnimationFrame的优势,在于充分利用显示器的刷新机制,比较节省系统资源。显示器有固定的刷新频率(60Hz或75Hz),也就是说,每秒最多只能重绘60次或75次,requestAnimationFrame的基本思想就是与这个刷新频率保持同步,利用这个刷新频率进行页面重绘。此外,使用这个API,一旦页面不处于浏览器的当前标签,就会自动停止刷新。这就节省了CPU、GPU和电力。 不过有一点需要注意,requestAnimationFrame是在主线程上完成。这意味着,如果主线程非常繁忙,requestAnimationFrame的动画效果会大打折扣。 requestAnimationFrame使用一个回调函数作为参数。这个回调函数会在浏览器重绘之前调用。

在搞懂了canvas绘制动画的原理和方法,我们来绘制几个动画:

奔跑的小恐龙

这个动画的原理很简单,就是使用setInterval()方法不断地添加渲染的图片(这里不需要清屏步骤,因为我们直接绘制新的图片覆盖了旧图片),让图片连贯起来,看起来像是动图。上代码:

代码语言:javascript复制 const canvas = document.getElementById('canvas') const ctx = canvas.getContext('2d') // 存储图片的是src: const imgSrcs = ['http://panpan.dapanna.cn//image-20221015115049427.png', 'http://panpan.dapanna.cn//image-20221015115033342.png', 'http://panpan.dapanna.cn//image-20221015115015133.png', 'http://panpan.dapanna.cn//image-20221015114950581.png', 'http://panpan.dapanna.cn//image-20221015114245445.png', 'http://panpan.dapanna.cn//image-20221015114437817.png', 'http://panpan.dapanna.cn//image-20221015114526684.png', 'http://panpan.dapanna.cn//image-20221015114610049.png', 'http://panpan.dapanna.cn//image-20221015114653366.png', 'http://panpan.dapanna.cn//image-20221015114722067.png', 'http://panpan.dapanna.cn//image-20221015114802665.png', 'http://panpan.dapanna.cn//image-20221015114927924.png'] const img = new Image() var i = 0 // 间隔70ms绘制一次图片,: setInterval(() => { img.src = imgSrcs[i] img.onload = () => { ctx.drawImage(img, 60, 120) // 绘制图片,这里看不懂的小伙伴建议去看我上一期写的canvas基础教程 } i++ if (i === 12) { i = 0 } }, 70)

绘制结果:

小恐龙

有的小伙伴可能会问,既然在前边讲了那么多setInterval()方法的缺点和requestAnimationFrame()方法的优势,为什么在这里绘制动画还要使用setInterval()方法呢?别急,等下我们会使用requestAnimationFrame()方法重新写一遍这个动画。

绘制钟表

如果你去浏览器百度“时间”两个字,你会发现网页上的时钟就是拿canvas写的:

那么我们也来尝试一下画一个时钟吧!

绘制钟表同样是遵循清屏→更新→渲染的原理,不过这里我们使用的是requestAnimationFrame()方法,大致思路就是使用requestAnimationFrame方法不断获取当前的时间,包括时、分、秒,并且根据获取的时间,结合时钟的‘针’所应旋转的角度,不断地清屏和重绘即可。详细思路直接看代码中的注释:

代码语言:javascript复制 const canvas = document.getElementById('canvas') const ctx = canvas.getContext('2d') // 绘制时钟显示之前的文本提示: ctx.font = '50px s' ctx.textAlign = 'center' ctx.strokeText('你即将看到时钟', 450, 400, 400) // 绘制时钟: function draw() { // 获取当前时间: const date = new Date() // 获取当前秒: let second = date.getSeconds() // 获取当前分: let minutes = date.getMinutes() // 获取当前时: let hour = date.getHours() // 每次循环都要线清空画布 ctx.clearRect(0, 0, canvas.width, canvas.height) ctx.save() // 保存状态1 ctx.translate(450, 400) // 移动画布原点 // 绘制时间刻度: for (i = 0; i < 60; i++) { ctx.save() // 保存状态2 ctx.beginPath() ctx.rotate([(Math.PI) / 180] * 6 * i) ctx.moveTo(0, -400) ctx.lineTo(0, -380) // 当刻度为5的整数倍的时候,加粗: if (i % 5 == 0) { // 绘制时钟上的时间刻度: ctx.save() // 保存状态3 ctx.translate(0, -350) ctx.rotate([-(Math.PI / 180)] * 6 * i) ctx.font = '30px s' ctx.textAlign = 'center' ctx.textBaseline = 'middle' ctx.fillText(`${i / 5 == 0 ? 12 : i / 5}`, 0, 0, 50) // 绘制出1-12刻度文字 ctx.restore() // 恢复状态3 // 让时间刻度为5的倍数的刻度加粗: ctx.lineWidth = 5 } ctx.stroke() ctx.restore() // 恢复状态2 } ctx.restore() // 恢复状态1 ctx.save() // 保存状态4 ctx.save() // 保存状态5 ctx.save() // 保存状态6 // 绘制时分秒针交点地方的小黑圆: ctx.beginPath() ctx.arc(450, 400, 400, 0, [(Math.PI) / 180] * 360) ctx.stroke() ctx.beginPath() ctx.arc(450, 400, 5, 0, [(Math.PI) / 180] * 360) ctx.fill() // 画秒针: ctx.beginPath() ctx.translate(450, 400) ctx.rotate([(Math.PI) / 180] * second * 6) // 换算秒针的旋转角度 ctx.moveTo(0, 0) ctx.lineTo(0, -320) ctx.strokeStyle = 'red' ctx.stroke() // 画时针: ctx.restore() // 恢复状态6 ctx.beginPath() ctx.translate(450, 400) ctx.rotate([hour * (Math.PI) / 180] * 3600 * 1 / 120) // 换算秒时针的旋转角度 ctx.rotate([minutes * (Math.PI) / 180] * 1 / 2) // 换算秒时针的旋转角度 ctx.rotate([(Math.PI) / 180] * second * 1 / 120) // 换算秒时针的旋转角度 ctx.moveTo(0, 0) ctx.lineTo(0, -100) ctx.lineWidth = 8 ctx.stroke() // 画分针: ctx.restore() // 恢复状态5 ctx.beginPath() ctx.translate(450, 400) ctx.rotate((Math.PI) * 2 * minutes / 60) // 换算分针的旋转角度 ctx.rotate([(Math.PI) / 180] * second * 1 / 10) // 换算分针的旋转角度 ctx.moveTo(0, 0) ctx.lineTo(0, -240) ctx.lineWidth = 4 ctx.strokeStyle = 'blue' ctx.stroke() ctx.beginPath() ctx.restore() // 恢复状态4 window.requestAnimationFrame(draw) } window.requestAnimationFrame(draw)

绘制结果:

时钟

⬆为了便于大家观看,具体步骤我写在了代码块的注释中

重绘小恐龙

我封装了一下requestAnimationFrame()方法,这样我们既可以用到requestAnimationFrame方法的优点,又可以自由控制每次调用绘制函数的时间间隔:

封装:

代码语言:javascript复制// 重新封装requestAnimationFrame函数: function mySetInterval(func, detay) { var i = 0 myReq = requestAnimationFrame(function fn() { // 判断现在处于60帧的第几帧,如果是目标帧的话,调用func函数: if (i % parseInt(60 / (1000 / detay)) == 0) { func(); } i++ // 让i值每秒增加60,循环调用func函数: requestAnimationFrame(fn) }) } // 调用封装好的函数,一秒钟打印一次'111': mySetInterval(function () { console.log(111); }, 1000)

这样我们就可以调用封装的mySetInterval方法来代替setInterval方法了:

代码语言:javascript复制 const canvas = document.getElementById('canvas') const ctx = canvas.getContext('2d') // 存储图片的链接: const imgSrcs = ['http://panpan.dapanna.cn//image-20221015115049427.png', 'http://panpan.dapanna.cn//image-20221015115033342.png', 'http://panpan.dapanna.cn//image-20221015115015133.png', 'http://panpan.dapanna.cn//image-20221015114950581.png', 'http://panpan.dapanna.cn//image-20221015114245445.png', 'http://panpan.dapanna.cn//image-20221015114437817.png', 'http://panpan.dapanna.cn//image-20221015114526684.png', 'http://panpan.dapanna.cn//image-20221015114610049.png', 'http://panpan.dapanna.cn//image-20221015114653366.png', 'http://panpan.dapanna.cn//image-20221015114722067.png', 'http://panpan.dapanna.cn//image-20221015114802665.png', 'http://panpan.dapanna.cn//image-20221015114927924.png'] const img = new Image() var i = 0 // 重新封装requestAnimationFrame函数: function mySetInterval(func, detay) { var i = 0 myReq = requestAnimationFrame(function fn() { // 判断现在处于60帧的第几帧,如果是目标帧的话,调用func函数: if (i % parseInt(60 / (1000 / detay)) == 0) { func(); } i++ // 让i值每秒增加60,循环调用func函数: requestAnimationFrame(fn) }) } // 不断绘制新的图片: mySetInterval(() => { img.src = imgSrcs[i] img.onload = () => { ctx.drawImage(img, 60, 120) } i++ if (i === 12) { i = 0 } }, 70)

显示:

小恐龙

以上就是canvas绘制基本动画的案例,高级动画(添加上物理效果,如下图⬇)的讲解会在下一期,有兴趣的小伙伴可以关注我,不定期发一些对你有用或好玩的干货内容!

小球物理



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有