跳到主要内容

id 选择器

按 id 查找返回单个节点。Konva 不强制 id 唯一,这一点与 DOM 不同。

用法​

要使用Konva通过id属性来选择形状,我们可以使用find()方法来搜索带有特定id属性的元素。 find()方法总是返回一个元素数组,即使我们期望它只返回一个元素。 如果你只需要一个元素,你可以使用findOne()方法。 find()方法适用于任何节点,包括舞台,图层,组和形状。

说明:按下“激活矩形”按钮,通过id选择矩形并执行过渡动画。 您也可以拖拽矩形。

<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/konva@10/konva.min.js"></script>
<meta charset="utf-8">
<title>Konva Select Shape by id Demo</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #F0F0F0;
}
#buttons {
position: absolute;
top: 5px;
left: 10px;
}
#buttons > input {
padding: 10px;
display: block;
margin-top: 5px;
}
</style>
</head>
<body>
<div id="container"></div>
<div id="buttons">
<input type="button" id="activate" value="Activate rectangle">
</div>
<script>
var width = window.innerWidth;
var height = window.innerHeight;

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

var layer = new Konva.Layer();
for(var n = 0; n < 10; n++) {
var circle = new Konva.Circle({
x: Math.random() * stage.getWidth(),
y: Math.random() * stage.getHeight(),
radius: Math.random() * 50 + 25,
fill: 'red',
strokeWidth: 3,
stroke : 'black'
});

layer.add(circle);
}

var rect = new Konva.Rect({
x: 300,
y: 90,
width: 100,
height: 50,
fill: 'green',
strokeWidth: 3,
offset: {
x: 50,
y: 25
},
draggable: true,
id: 'myRect'
});

layer.add(rect);
stage.add(layer);

var tween;

document.getElementById('activate').addEventListener('click', function() {
// or var shape = stage.findOne('#myRect');
var shape = stage.find('#myRect')[0];

if (tween) {
tween.destroy();
}

tween = new Konva.Tween({
node: shape,
duration: 1,
scaleX: Math.random() * 2,
scaleY: Math.random() * 2,
easing: Konva.Easings.ElasticEaseOut
}).play();
}, false);
</script>

</body>
</html>

常见问题​

Konva 会检查 id 重复吗?​

不会。两个节点用同一个 id 完全不会报错,但 findOne('#x') 只返回 遍历中遇到的第一个,另一个就像不存在一样。

这类 bug 很难查——代码看起来完全正确,只是操作的不是你以为的那个节点。 批量创建节点时尤其要小心,id 应该带上唯一的序号或数据主键。

find('#x')[0] 和 findOne('#x') 有区别吗?​

结果一样,但 findOne() 一旦找到就停止遍历,find() 会走完整棵树 再返回数组。节点多时后者明显更慢。

只要一个结果就用 findOne()。另外 find() 在没找到时返回空数组, [0] 是 undefined,与 findOne() 的返回值一致,这一点倒是没有差别。

选择器是怎么实现的?​

是遍历,不是哈希查找。每次调用都会从起点节点开始走一遍子树, 逐个比对。

这意味着它的成本与节点总数成正比,而不是像 DOM 的 getElementById 那样接近常数时间。在动画回调或高频事件里反复调用选择器, 是很容易被忽略的性能问题。

性能提示​

选择器的开销随节点数量线性增长,而它经常被放在不该放的地方。

把引用缓存下来。创建节点时就存好变量或存进 Map,之后直接用, 不要每次都查。尤其是在 dragmove、transform、动画回调这类每帧执行的地方, 一次选择器调用乘以 60 就很可观了。

缩小查找范围。group.findOne('#x') 只遍历这个分组, 比 stage.findOne('#x') 遍历全树快得多。知道节点在哪个子树里就从那里找。

大量节点考虑自建索引。上千个节点时,维护一个 Map<id, node> 把查找降到常数时间,比依赖选择器可靠。代价是增删节点时要同步维护这个表。