Skip to main content

HTML5 Canvas 全部 Konva 性能优化技巧列表

不想把时间浪费在性能问题上?可以请求一份性能评估

为什么这很重要

HTML5 canvas 在其功能范围内效率很高,Konva 内部也有许多旨在提供良好性能的功能。然而,当你的项目开始变得复杂,或者舞台上有大量形状时,性能必然会受到一定影响。

优化目标

这里的优化聚焦于两个基本原则:

  • 尽可能少计算:所有计算都需要时间完成。每一次计算可能只消耗极短时间,但成千上万甚至数百万次的计算——无论是你的代码、Konva、JavaScript,还是底层的图层——累计起来,如果那个超流畅的动画或效果出现卡顿,就能被肉眼察觉。

  • 尽可能少绘制:这是关键,因为所有绘制都有性能成本。成本可以分为两类——首先是绘制所需计算时间(上述提到的计算部分),其次是将绘制内容从内存移动到屏幕所需时间。某些情况下,还可能涉及中间的离屏合成或每像素处理。因此,原则是尽可能少绘制。

舞台(Stage)

  1. 优化舞台大小 — 遵循“尽可能少绘制”原则,避免创建过大的舞台,因为从内存传输大量字节至屏幕会产生性能负担。这里有一些针对超大舞台问题的技巧参考此处

  2. 在移动设备上设置视口 — 缩放图像会带来重大性能打击,因此在移动端应用中,设置视口标签:<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">,以避免 Konva 输出被不必要地缩放。

  3. 在视网膜设备上使用 Konva.pixelRatio = 1 — Konva 会自动调整像素比例以保证在各种设备上绘制锐利图像。但如果视网膜设备性能不佳,可以设置 Konva.pixelRatio = 1 来减少 Konva 的缩放开销。此设置在某些情况下可能影响输出效果,请确保最终画质符合你的需求。

图层(Layers)

  1. 图层管理 — 在底层,每个 Konva 图层相当于一个独立的 HTML5 canvas 元素,这带来了实用的能力,比如只刷新变更的图层以避免重绘整个舞台所消耗的性能。但强力带来责任,每个图层都会增加额外的性能开销,因此应将图层数量保持在较低水平。

  2. 使用 layer.listening(false) — Konva 会在所有绘制的形状上监听鼠标和触摸事件,但每个监听都会消耗性能。图层中形状数量多时,Konva 需要花费大量时间来检查哪些监听器可能被触发。如果图层上的形状都不需要响应事件,可以通过 layer.listening(false) 关闭该层的事件监听,减轻负担。示例见演示。形状部分也有类似建议。

  3. 优化拖动性能 — 拖动形状时,相关图层需在每个拖动事件周期重绘,为避免此性能成本,可拖动时将形状移动至专用图层,拖动结束再将其移回原图层。示例见演示

形状(Shapes)

  1. 形状缓存 — Konva 内部会创建形状的图像缓存,并在需要时直接绘制该缓存。绘制图像相较于从绘制指令重组形状来说开销更小,能显著提升复杂形状或组的性能。

  2. 保持形状整洁 — 舞台上的每个形状存在都会消耗性能。为优化性能,应隐藏或从图层中移除那些已不可见(opacity=0)或超出视野的对象。

  3. 使用 shape.listening(false) — 同图层(见上文第 7 点),Konva 会监听形状事件,这存在性能成本。告知形状不监听事件可减少此开销,详见关闭监听

  4. 关闭完美绘制 — 有时 HTML5 canvas 绘制结果可能不符合预期,详见示例关闭完美绘制。Konva 通过完美绘制功能做了额外工作以修正,但这带来性能成本。设置 shape.perfectDrawEnabled(false) 可避免此成本,且在形状有填充、描边和不透明度的情况下不会降低质量。

  5. 优化描边绘制 — 为了实现预期绘制效果,当形状同时拥有描边和阴影时,Konva 会做额外的内部绘制。通过关闭 Konva 为描边添加的阴影,可以避免这一性能开销。

动画(Animations)

  1. 优化动画 — 避免为无视觉变化的动画步骤执行不必要的重绘。

内存(Memory)

  1. 避免内存泄漏 — Konva 已尽可能处理各种内存泄漏情况,但在创建形状和补间动画时,以及管理它们销毁时,你仍需要注意避免泄漏。

下面有个示例演示了上述部分性能优化技巧:

import Konva from 'konva';

// 创建具有良好性能配置的舞台
const stage = new Konva.Stage({
  container: 'container',
  width: window.innerWidth,
  height: window.innerHeight,
});

// 创建带性能优化的图层
const backgroundLayer = new Konva.Layer({ listening: false });
const mainLayer = new Konva.Layer();
const dragLayer = new Konva.Layer();

stage.add(backgroundLayer);
stage.add(mainLayer);
stage.add(dragLayer);

// 创建一个启用缓存的形状
const star = new Konva.Star({
  x: 200,
  y: 200,
  numPoints: 6,
  innerRadius: 40,
  outerRadius: 70,
  fill: 'yellow',
  stroke: 'black',
  strokeWidth: 4,
  draggable: true,
  perfectDrawEnabled: false, // 性能优化
});

// 缓存形状以提升性能
star.cache();

// 优化拖动性能
star.on('dragstart', () => {
  star.moveTo(dragLayer);
});

star.on('dragend', () => {
  star.moveTo(mainLayer);
});

// 创建关闭监听的背景形状
const rect = new Konva.Rect({
  x: 0,
  y: 0,
  width: stage.width(),
  height: stage.height(),
  fill: 'lightgray',
  listening: false,
});

backgroundLayer.add(rect);
mainLayer.add(star);