canvas进阶
1.动画
在Canvas中,动画其实也就是一些基础的几何变换
,因此想做动画第一步咱们需要先了解有哪些几何变换
。
几何变换
几何变换的类型其实和CSS动画中的类型差不多,也就是:移动、旋转、缩放
移动
语法:translate(x, y)
,其中 x 是左右偏移量,y 是上下偏移量。
const canvas = document.getElementById('canvas'); // 获取Canvas |
旋转
语法:rotate(angle)
,其中 angle 是旋转的角度,以弧度为单位,顺时针旋转。
const canvas = document.getElementById('canvas'); // 获取Canvas |
每次调用 rotate()
方法都是基于当前绘图上下文的状态进行旋转,就是在上一次旋转的角度基础上再进行旋转。用closePath()也不能重置旋转的角度为0°
缩放
语法:scale(x, y)
,其中 x 为水平缩放的值,y 为垂直缩放得值。x和y的值小于1则为缩小,大于1则为放大。默认值为 1。
const canvas = document.getElementById('canvas'); // 获取Canvas |
状态的保存和恢复
什么是状态的保存和恢复呢?我们这么理解,当我们在Canvas中绘制时,每次绘制完都会是一个Canvas的快照,而每个快照时的状态,我们可以保存起来,当我们需要再次使用时,又把这个快照恢复。
状态的保存和恢复 用到的方法是 save()
和 restore()
, 分别是保存和恢复。方法不需要参数,直接调用就OK。
绘画的状态有哪些呢(就是我们可以保存和恢复的状态有哪些)?我们列举一下:
- 应用的变形:移动、旋转、缩放、strokeStyle、fillStyle、globalAlpha、lineWidth、lineCap、lineJoin、miterLimit、lineDashOffset、shadowOffsetX、shadowOffsetY、shadowBlur、shadowColor、globalCompositeOperation、font、textAlign、textBaseline、direction、imageSmoothingEnabled等。
- 应用的裁切路径(clipping path)
const canvas = document.getElementById('canvas'); // 获取Canvas |
如上图我们可以看出,最开始我们设置了填充颜色为灰色,并绘制了一个矩形,然后我们执行了状态保存,上面我们已经列举了哪些状态可以保存,所以这里我们知道此次的状态保存的是:fillStyle
状态,保存完以后我们又设置了填充颜色为橘色,并且又绘制了一个矩形,最后我们执行了一次状态恢复,接着直接绘制一个正方形。我们知道如果没有状态保存和恢复的方法,正常情况下正方形应该是使用橘色来填充,但正因为我们保存了fillStyle
状态的灰色,又在绘制正方形之前恢复了fillStyle
状态为灰色,因此绘制出来的正方形为灰色。
2.动画
Canvas呈现的东西都是绘制完了以后才能看到,因此想通过Canvas自己提供的Api来实现动画是做不到的。
那么想在 Canvas 中实现动画就得借助别的东西,那么借助啥呢?
在我们的 windows 对象上有三个方法:
- setInterval(function, delay) :定时器,当设定好间隔时间后,function 会定期执行。
- setTimeout(function, delay):延时器,在设定好的时间之后执行函数
- requestAnimationFrame(callback):告诉浏览器你希望执行一个动画,并在重绘之前,请求浏览器执行一个特定的函数来更新动画。
那么这三个方法有什么区别呢?
正常情况下,当我们需要自动去展示动画而不需要和用户交互的情况下,我们会选择 setInterval()
方法,因为我们只需要把执行动画的代码丢在 setInterval()
方法中,他就会自动执行绘制我们想要的动画。如果我们做一些交互性的动画,那么使用 setTimeout()
方法和键盘或者鼠标事件配合会更简单一些。相对于前两个方法,requestAnimationFrame()
方法可能会显得陌生一些,requestAnimationFrame()
方法提供了更加平缓且有效率的方式来执行动画,当我们准备好动画以后,把动画交给requestAnimationFrame()
方法就能绘制动画帧。
setInterval setTimeout
这里先使用 setInterval()
方法实现一个元素的位移效果。
const canvas = document.getElementById('canvas'); // 获取Canvas |
如图我们可以看出,元素确实动了,但是似乎不是我们想要的那个样子,我们想实现的是元素的位移,但看样子实现的是元素的变宽。
那么我们看一下问题出在哪里?
经过我们的一番思考,我们发现,Canvas 绘制时把元素一帧一帧的绘制到画布上,比如上面的例子我们把一个元素从(0,0)移动到(400,0),也就是横向移动400像素。既然是一帧一帧绘制的,那么我们看到的就是连续的从(0,0)绘制到(400,0)的效果,也就是我们看到的是所有的帧组合在一起的效果,而不是从(0,0)移动到(400,0)的效果。
那么想要看到移动的效果就需要我们只看此时此刻的那一帧,而不看之前的帧。因此我们在绘制下一帧的同时我们需要把上一帧清除掉。
画布清空
语法:clearRect(x, y, width, height)
参数:
- x为要清除的矩形区域左上角的x坐标,
- y为要清除的矩形区域左上角的y坐标
- width为要清除的矩形区域的宽度
- height为要清除的矩形区域的高度
const canvas = document.getElementById('canvas'); // 获取Canvas |
requestAnimationFrame
requestAnimationFrame()
方法的整体性能要比setInterval()
方法好很多,打个比方,当我们用setInterval()
方法来做动画,我们需要设置一下多长时间执行一次setInterval()
方法里面的代码块,而这个时间我们只要设定了,那么就会强行这个时间执行,而如果我们的浏览器显示频率和setInterval()
方法执行的绘制请求不一致,就会导致一些帧率消失,从而造成卡顿的效果。因此使用requestAnimationFrame()
方法做动画会更加平缓且有效率。
同时在requestAnimationFrame()
方法的使用中我们需要注意,一般每秒钟回调函数执行次数为60次,但也可能会被降低,因为通常情况下requestAnimationFrame()
方法会遵循W3C的建议,在浏览器中的回调函数执行次数需要和浏览器屏幕刷新次数相匹配。还有就是为了提高性能和电池使用寿命,requestAnimationFrame()
方法运行在后台标签页或者隐藏在 <iframe>
标签里时,requestAnimationFrame()
方法会暂停调用以提升性能和电池使用寿命。
requestAnimationFrame()
方法不能自循环,那怎么让他实时触发渲染呢?
function callbackFn() { |
这样就形成一个递归,当执行完以后会自动调用它自己。
const canvas = document.getElementById('canvas'); |