跳到主要内容

图层管理

每个图层是一张独立的 canvas。合理分层,可以让「只有一小块在动」时不必重绘整个画面。

用法​

当创建Konva应用程序时,在性能方面最需要的考虑就是层的管理. 让Konva从其他画布库中脱颖而出的一大特点就是, 它使我们能够创建单独的层,每个层都有自己对应的画布元素。这意味着我们可以只动画,转换或更新舞台上的一部分元素,同时不需要重新绘制舞台上其他的元素。如果我们检查一个Konva舞台的DOM结构,我们将看到实际上每一个层都有一个对应的画布元素. 本教程有两个图层,一个是动画图层,另一个是包含文本的静态图层。 由于在动画中不需要重绘文本, 所以可以把它放置在一个单独的图层中.

下面的示例中有两个层, 一个是动画层, 一个是包含文本的静态层. 由于文本上没有动画, 不需要被连续地重绘, 因此被放在一个单独的层里.

<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/konva@10/konva.min.js"></script>
<meta charset="utf-8">
<title>Konva Layer Management Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #F0F0F0;
}
</style>
</head>
<body>
<div id="container"></div>

<script>
var width = window.innerWidth;
var height = window.innerHeight;

var stage = new Konva.Stage({
container: 'container',
width: width,
height: height
});
var animLayer = new Konva.Layer();
var staticLayer = new Konva.Layer();

/*
* leave center point positioned
* at the default which is at the center
* of the hexagon
*/

var blueHex = new Konva.RegularPolygon({
x: 50,
y: stage.height() / 2,
sides: 6,
radius: 40,
fill: '#00D2FF',
stroke: 'black',
strokeWidth: 4,
draggable: true
});

var yellowHex = new Konva.RegularPolygon({
x: stage.width() / 2,
y: stage.height() / 2,
sides: 6,
radius: 30,
fill: 'yellow',
stroke: 'black',
strokeWidth: 4,
draggable: true
});

/*
* move center point to right side
* of hexagon
*/
var redHex = new Konva.RegularPolygon({
x: 250,
y: stage.height() / 2,
sides: 6,
radius: 30,
fill: 'red',
stroke: 'black',
strokeWidth: 4,
offset: {
x: 30,
y: 0
},
draggable: true
});

var text = new Konva.Text({
x: 10,
y: 10,
text: 'Static Layer',
fontSize: '30',
fontFamily: 'Calibri',
fill: 'black'
});

staticLayer.add(text);

animLayer.add(blueHex, yellowHex, redHex);
stage.add(animLayer, staticLayer);

var period = 2000;
var anim = new Konva.Animation(function(frame) {
var scale = Math.sin(frame.time * 2 * Math.PI / period) + 0.001;
// scale x and y
blueHex.scale({x : scale, y : scale});
// scale only y
yellowHex.scaleY(scale);
// scale only x
redHex.scaleX(scale);
}, animLayer);

anim.start();
</script>

</body>
</html>

常见问题​

图层是不是越多越好?​

不是。每个 Konva.Layer 都会创建一张与舞台等大的 canvas——实际上是两张,场景图和命中图。

一个 1920×1080 的舞台,单张 canvas 的显存占用就是几 MB,高 DPI 屏幕下还要乘以 pixelRatio 的平方。图层开到十几个,内存会很可观,而且浏览器合成这些图层本身 也有成本。

实践中个位数的图层足够表达绝大多数场景。

怎么划分图层?​

按更新频率分,不是按业务逻辑分。

典型的三层结构:静态背景(网格、底图)一层,几乎不重绘;主体内容一层; 交互反馈(拖拽中的元素、选择框、辅助线)一层,每帧都在变。

拖拽时把被拖的节点临时移到最上层,松手后移回去——这样拖拽过程中只有 那一层在重绘,主体内容原封不动。

图层顺序怎么调整?​

图层之间用 moveToTop()、zIndex() 这些方法,和普通节点一样。

要注意的是跨图层的层级由图层顺序决定,节点自己的 zIndex 管不着。 一个节点在图层 A 里 moveToTop(),它也只是 A 里面最上面的, 仍然会被图层 B 的内容盖住。这是分层最容易造成困惑的地方。

性能提示​

分层的收益来自避免无谓重绘,所以它只在「画面大部分静止」时才有意义。 如果每帧所有内容都在变,分层不但不省,还多了合成开销。

几个实操要点:

静态层要真的静态。背景层里混进一个会动的元素,整层就白分了。 检查办法是给每层的 draw 打点计数,看静态层是不是真的不重绘。

拖拽时临时提层。node.moveTo(dragLayer) 把节点搬到专用图层, dragend 时搬回。这是 Konva 应用里收益最稳定的一项优化。

不监听的图层设 listening: false。Konva 10.3.2 起这类图层会释放掉 与舞台等大的命中画布,内存收益直接可见。