我正在用画布开发一个简单的 2D 浏览器游戏,但我已经到了需要实现一个简单用户界面的地步。具体来说,我有一个快捷栏,用户可以点击它来选择一个项目。在游戏世界中单击也会使用您在该位置选择的项目。所以当前的问题是点击快捷栏也会导致它在游戏世界中点击。
相反,如果用户点击快捷栏(只是一个用画布绘制的矩形),我希望快捷栏处理该 mousedown
事件并使其停止向上传播到游戏画布元素,这样只有该项目被选中,并且该项目没有也使用。
因此,我为我的自定义用户界面元素开发了一个类似于 addEventListener
的功能,但存在一个问题:它非常慢。
例如,mousedown
运行良好。当用户点击某个位置时,它会遍历父层次结构以找到最顶层的元素,如下所示:
getTopMostElement(x, y) {
let root = this.rootElement;
for (let i = 0; i < root.children.length; i++) {
let child = root.children[i];
let bounds = child.getBounds();
if (bounds && rectangleContainsPoint(bounds.x, bounds.y, bounds.w, bounds.h, x, y)) {
i = 0;
root = child;
}
}
return root;
}
然后检查该元素是否具有 mouseDown
侦听器,如果有,则调用它。如果该侦听器调用 stopPropagation
,则它停止传播。否则,它会向上迭代该元素的父层次结构,依次调用每个侦听器。
问题在于 mousemove
之类的事件。这个 O(n)
操作实在是太慢了。也就是说,每次用户移动鼠标时,都要向下迭代整个元素层次结构,找到鼠标位置最顶层的元素,然后向上迭代。有没有更有效的方法来做到这一点?浏览器如何做到这一点?目前我只有 10-20 个 UI 元素,所以我认为即使是空间分区或空间散列之类的东西也不会比简单的 for 循环更有效。
答案 0 :(得分:0)
公平地说,浏览器的优势在于能够 a) 运行本机代码和 b) 不必处理 JS 和本机 DOM 元素之间的绑定,例如获取边界/儿童/....
虽然我不完全确定浏览器是如何做到这一点的(尽管我相信您可以找到关于此的文档或研究论文),但我立即想到了一些想法:
您可能会想到更多优化,因为您对实现的细节了如指掌。例如。对于鼠标单击/传播,它是否从最顶层元素开始,然后通过其祖先传播?或者它是否通过同一位置的其他元素“传播”?例如:
root (z-index 0)
|- div A (z-index 2)
|- div B (z-index 1)
|- button (z-index 3)
想象一下,由于某种原因,您单击了一个 X/Y 位置,其中 div A、div B 和按钮都在边界内。由于按钮是最顶层呈现的元素,您的事件处理从那里开始。但是接下来会去哪里呢? button > div B > root
?还是button > div A > div B > root
? 例如传播分层或可视化。 像这样的决策会影响您的“框架”的工作方式以及优化它的方式。