性能篇29 # 怎么给Canvas绘制加速?
Posted 凯小默
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了性能篇29 # 怎么给Canvas绘制加速?相关的知识,希望对你有一定的参考价值。
说明
【跟月影学可视化】学习笔记。
方法一:优化 Canvas 指令
例子:实现一些位置随机的多边形,并且不断刷新这些图形的形状和位置
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>优化 Canvas 指令</title>
<style>
canvas
border: 1px dashed #fa8072;
</style>
</head>
<body>
<canvas width="500" height="500"></canvas>
<script>
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
// 创建正多边形,返回顶点
function regularShape(x, y, r, edges = 3)
const points = [];
const delta = (2 * Math.PI) / edges;
for (let i = 0; i < edges; i++)
const theta = i * delta;
points.push([
x + r * Math.sin(theta),
y + r * Math.cos(theta),
]);
return points;
// 根据顶点绘制图形
function drawShape(context, points)
context.fillStyle = "red";
context.strokeStyle = "black";
context.lineWidth = 2;
context.beginPath();
context.moveTo(...points[0]);
for (let i = 1; i < points.length; i++)
context.lineTo(...points[i]);
context.closePath();
context.stroke();
context.fill();
// 多边形类型,包括正三角形、正四边形、正五边形、正六边形和正100边形、正500边形
const shapeTypes = [3, 4, 5, 6, 100, 500];
const COUNT = 1000;
// 执行绘制
function draw()
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < COUNT; i++)
const type =
shapeTypes[
Math.floor(Math.random() * shapeTypes.length)
];
const points = regularShape(
Math.random() * canvas.width,
Math.random() * canvas.height,
10,
type
);
drawShape(ctx, points);
requestAnimationFrame(draw);
draw();
</script>
</body>
</html>
我们f12查看帧率,效果如下:Google Chrome浏览器怎么开启查看帧率功能?
对于一个 500 边形来说,它的顶点数量非常多,所以 Canvas 需要执行的绘图指令也会非常多,那绘制很多个 500 边形自然会造成性能问题。
下面减少绘制 500 边形的绘图指令的数量:用 -1 代替正 500 边形,如果type小于0表名多边形是正500边形,用 arc 指令来画圆
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>优化 Canvas 指令</title>
<style>
canvas
border: 1px dashed #fa8072;
</style>
</head>
<body>
<canvas width="500" height="500"></canvas>
<script>
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
// 创建正多边形,返回顶点
function regularShape(x, y, r, edges = 3)
const points = [];
const delta = (2 * Math.PI) / edges;
for (let i = 0; i < edges; i++)
const theta = i * delta;
points.push([
x + r * Math.sin(theta),
y + r * Math.cos(theta),
]);
return points;
// 根据顶点绘制图形
function drawShape(context, points)
context.fillStyle = "red";
context.strokeStyle = "black";
context.lineWidth = 2;
context.beginPath();
context.moveTo(...points[0]);
for (let i = 1; i < points.length; i++)
context.lineTo(...points[i]);
context.closePath();
context.stroke();
context.fill();
// 多边形类型,包括正三角形、正四边形、正五边形、正六边形和正100边形以及正500边形
// 用 -1 代替正 500 边形
const shapeTypes = [3, 4, 5, 6, 100, -1];
const COUNT = 1000;
const TAU = Math.PI * 2;
// 执行绘制
function draw()
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < COUNT; i++)
const type =
shapeTypes[
Math.floor(Math.random() * shapeTypes.length)
];
const x = Math.random() * canvas.width;
const y = Math.random() * canvas.height;
// 如果type小于0表名多边形是正500边形
if(type > 0)
// 画正多边形
const points = regularShape(x, y, 10, type);
drawShape(ctx, points);
else
// 画圆
ctx.beginPath();
// 绘制正多边形,否则用 arc 指令来画圆
ctx.arc(x, y, 10, 0, TAU);
ctx.stroke();
ctx.fill();
requestAnimationFrame(draw);
draw();
</script>
</body>
</html>
优化完之后的效果:
方法二:使用缓存
具体做法就是将图形缓存下来,保存到离屏的 Canvas(offscreen Canvas)中,然后在绘制的时候作为图像来渲染,那就可以将绘制顶点的绘图指令变成直接通过 drawImage
指令来绘制图像,而且也不需要 fill()
方法来填充图形。
https://developer.mozilla.org/zh-CN/docs/Web/API/OffscreenCanvas
OffscreenCanvas 提供了一个可以脱离屏幕渲染的 canvas 对象。它在窗口环境和web worker环境均有效。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>使用缓存</title>
<style>
canvas
border: 1px dashed #fa8072;
</style>
</head>
<body>
<canvas width="500" height="500"></canvas>
<script>
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
// 创建缓存的函数
function createCache()
const ret = [];
for (let i = 0; i < shapeTypes.length; i++)
// 创建离屏Canvas缓存图形
const cacheCanvas = new OffscreenCanvas(20, 20);
// 将图形绘制到离屏Canvas对象上
const type = shapeTypes[i];
const context = cacheCanvas.getContext("2d");
context.fillStyle = "red";
context.strokeStyle = "black";
if (type > 0)
const points = regularShape(10, 10, 10, type);
drawShape(context, points);
else
context.beginPath();
context.arc(10, 10, 10, 0, TAU);
context.stroke();
context.fill();
ret.push(cacheCanvas);
// 将离屏Canvas数组(缓存对象)返回
return ret;
// 创建正多边形,返回顶点
function regularShape(x, y, r, edges = 3)
const points = [];
const delta = (2 * Math.PI) / edges;
for (let i = 0; i < edges; i++)
const theta = i * delta;
points.push([
x + r * Math.sin(theta),
y + r * Math.cos(theta),
]);
return points;
// 根据顶点绘制图形
function drawShape(context, points)
context.fillStyle = "red";
context.strokeStyle = "black";
context.lineWidth = 2;
context.beginPath();
context.moveTo(...points[0]);
for (let i = 1; i < points.length; i++)
context.lineTo(...points[i]);
context.closePath();
context.stroke();
context.fill();
// 多边形类型,包括正三角形、正四边形、正五边形、正六边形和正100边形以及正500边形
const shapeTypes = [3, 4, 5, 6, 100, 500];
const COUNT = 1000;
const TAU = Math.PI * 2;
// 一次性创建缓存,直接通过缓存来绘图
const shapes = createCache();
function draw()
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < COUNT; i++)
const shape = shapes[Math.floor(Math.random() * shapeTypes.length)];
const x = Math.random() * canvas.width;
const y = Math.random() * canvas.height;
ctx.drawImage(shape, x, y);
requestAnimationFrame(draw);
draw();
</script>
</body>
</html>
开启缓存效果:
缓存的局限性:
- 如果需要创建大量的离屏 Canvas 对象,就会对内存消耗就非常大,有可能反而降低了性能。
- 缓存适用于图形状态本身不变的图形元素,如果是经常发生状态改变的图形元素,起不到减少绘图指令的作用。
- 不使用缓存直接绘制的是矢量图,而通过缓存 drawImage 绘制出的则是位图,所以缓存绘制的图形,在清晰度上可能不是很好。
方法三:分层渲染
简单点说就是用两个 Canvas 叠在一起,将不变的元素绘制在一个 Canvas 中,变化的元素绘制在另一个 Canvas 中。
满足两个条件:
- 一是有大量静态的图形元素不需要重新绘制
- 二是动态和静态图形元素绘制顺序是固定的,先绘制完静态元素再绘制动态元素
上面就是两个canvas,一个动的,一个静态的,我们把它们叠在一起
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<<以上是关于性能篇29 # 怎么给Canvas绘制加速?的主要内容,如果未能解决你的问题,请参考以下文章