基本使用
在 HTML 文件中创建一个 <canvas>
标签
html
<canvas id="myCanvas" width="500" height="400"></canvas>
获取<canvas>
元素,然后调用getContext('2d')
获得绘图的2D上下文对象:
js
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
示例:绘制一个红色矩形:
js
// 设置填充颜色
ctx.fillStyle = 'red';
// 绘制矩形(x, y, 宽, 高)<- 左上角的点(x, y)
ctx.fillRect(50, 50, 200, 100);
示例:绘制线条:
js
ctx.strokeStyle = 'blue';
ctx.lineWidth = 5;
ctx.beginPath();// 开始路径
ctx.moveTo(50, 50);// 起点
ctx.lineTo(250, 150);// 终点
ctx.stroke();// 绘制线条/多边形边框要用stroke
示例:绘制圆形(利用arc):
js
ctx.beginPath();
ctx.arc(150, 150, 50, 0, Math.PI * 2);
ctx.fillStyle = 'green';
ctx.fill();
实例:绘制三角形
js
<canvas id="myCanvas" width="500" height="400"></canvas>
const canvas = document.getElementById('myConvas')
const ctx = canvas.getContext('2d')
ctx.beginPath()
ctx.moveTo(30, 30) // 第一个点
ctx.lineTo(40, 40) // 第二个点
ctx.lineTo(20, 40) // 第三个点
ctx.closePath() // 封闭路径
ctx.fillStyle = 'red'
ctx.fill()
状态保存与恢复
ctx.save()
: 将当前的绘图状态压入栈中。ctx.restore()
: 从栈中弹出状态,恢复到上一个保存的状态。
js
ctx.save(); // 保存状态 A
ctx.fillStyle = 'blue';
ctx.fillRect(100, 500, 50, 50);
ctx.save(); // 保存状态 B (此时包含蓝色填充) ctx.fillStyle = 'yellow';
ctx.fillRect(150, 500, 50, 50);
ctx.restore(); // 恢复到状态 B (此时颜色又变回蓝色,因为之前保存的是蓝色填充)
ctx.restore(); // 恢复到状态 A (此时颜色是默认的黑色,或者之前设置的其他值)
几何变化
平移(Translate)
js
// 绘制一个未进行平移的红色矩形
context.fillStyle = 'red';
context.fillRect(10, 10, 50, 50);
// 保存当前状态
context.save();
// 将原点向下移动 100px,向右移动 100px
context.translate(100, 100);
// 这个矩形现在将相对于新的原点 (100, 100) 绘制
context.fillStyle = 'blue';
context.fillRect(10, 10, 50, 50);
// 实际会绘制在 (110, 110) 的位置
// 恢复状态
context.restore();
旋转(Rotate)
js
context.rotate(Math.PI / 4);
- 正值表示顺时针旋转。
- 负值表示逆时针旋转。
缩放(Scale)
js
context.scale(x, y)
x
: 水平方向上的缩放因子。y
: 垂直方向上的缩放因子。
requestAnimationFrame
使用 setTimeout
或 setInterval
来实现 Canvas 动画 和 requestAnimationFrame
的对比:
使用定时器动画干活,实际上是可以的,但是存在一个最大的问题,就是动画会抖动
,体验效果不是非常好。
而使用requestAnimationFrame
去做动画,就不会出现抖动的现象。
requestAnimationFrame
和js
中的setTimeout
定时器函数基本一致
,不过setTimeout
可以自由设置间隔时间,而requestAnimationFrame
的间隔时间是由浏览器自身决定的,大约是17毫秒
左右
那为什么定时器会卡顿呢?
1. 为什么定时器会卡
- 我们在手机或者电脑显示屏上看东西时,显示屏会默默的不停地干活(刷新画面)
- 这个刷新值得是每秒钟刷新次数,普通显示器的刷新率约为60Hz(每秒刷新60次),高档的有75Hz、90Hz、120Hz、144Hz等等
- 刷新率次数越高,显示器显示的图像越清晰、越流畅、越丝滑
- 不刷新就是静态的画面,刷新比较低就是
卡了
,PPT
的感觉 - 动画想要丝滑流畅,需要卡住时间点进行代码操作(代码语句赋值、浏览器重绘)
- 所以只需要每隔1000毫秒的60分之一(60HZ)即约为17毫秒,进行一次动画操作即可
- 只要卡住这个17毫秒,每隔17毫秒进行操作,就能确保动画丝滑
- 但是定时器的回调函数,会受到
js
的事件队列宏任务、微任务影响,可能设定的是17毫秒执行一次,但是实际上这次是17毫秒、下次21毫秒、再下次13毫秒执行,所以并不是严格的卡住了这个60HZ的时间 - 没有在合适的时间点操作,就会出现:类似这样的情况:
变
、不变
、不变
、变
、不变
... - 于是就出现了,绘制不及时的情况,就会有抖动的出现(以上述案例,位置和时间没有线性对应更新变化导致看起来抖动)
2. 为何requestAnimationFrame
不会卡
setTimeout
和setInterval
的问题是,它们都不精确。它们的内在运行机制决定了时间间隔,参数实际上只是指定了把动画代码添加到浏览器UI
线程队列中以等待执行的时间。如果队列前面已经加入了其他任务,那动画代码就要等前面的任务完成后再执行。
window.requestAnimationFrame(callback)
能够做到,精准严格的卡住显示器刷新的时间,比如普通显示器60HZ
它会自动对应17ms
执行一次,高级显示器120HZ
,它会自动对应9ms
执行一次。callback
函数的执行频率会与屏幕刷新同步。
requestAnimationFrame
只会执行一次,想要使其多次执行,要写成递归的形式。
所以,这就是requestAnimationFrame
的好处,window.requestAnimationFrame
这个api
就是解决了定时器不精准的问题的。
示例:用 requestAnimationFrame()
实现的小球左右移动动画
html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<title>requestAnimationFrame动画示例</title>
</head>
<body>
<canvas id="myCanvas" width="500" height="200" style="border:1px solid #000;"></canvas>
<script>
const canvas = document.getElementById('myCanvas');
const ctx = canvas.getContext('2d');
let x = 50; // 小球横坐标
const y = 100; // 小球纵坐标
const radius = 20; // 小球半径
let speed = 2; // 每帧移动距离
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height); // 清空画布
// 绘制小球
ctx.beginPath();
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fillStyle = 'blue';
ctx.fill();
// 更新位置
x += speed;
// 反弹边界检测
if (x + radius > canvas.width || x - radius < 0) {
speed = -speed; // 改变方向
}
// 下一帧动画请求
requestAnimationFrame(draw);
}
// 启动动画
requestAnimationFrame(draw);
</script>
</body>
</html>