Javascript:在创建/附加到DOM时注册*每个* DOM元素

Javascript: Registering *every* DOM Element on creation/appending to DOM

本文关键字:DOM 元素 注册 每个 创建 Javascript      更新时间:2023-09-26

为了在每个网站上保持正确和高度响应的GUI覆盖,我需要尽快注册和分析每个相关的DOM元素。目前我正在使用MutationObserver,它为我完成了这项工作并简化了,它看起来像这样:

var observer = new MutationObserver(
    function(mutations){
        mutations.forEach(
            function(mutation){
                if(mutation.type == 'childList')
                {
                    var nodes = mutation.addedNodes;
                    var n = nodes.length;
                    for(var i = 0; i < n; i++)
                    {
                        if(nodes[i].nodeType == 1) // ELEMENT_NODE
                        {
                            // Save it in specific Array or something like this
                            AnalyzeNode(nodes[i]);
                        }
                    }
                }
            }
        );
    }
);
var config = {subtree: true, childList: true}; 
observer.observe(document, config);

但是我已经意识到,所使用的MutationObserver并没有为包含在DOM中的每个节点调用AnalyzeNode。当一个已经完成的(子)树是在DOM之外创建的(例如,通过在页面加载时执行外部JS脚本),你把它的根添加到DOM 突变。addedNodes将只包含子树的根,它的所有子树都不会被注意到(因为那里不会发生进一步的变化),它们是DOM的一部分,但没有被分析。

我想检查附加的节点是否已经具有childNodes以将其标识为附加子树的根,但不幸的是,似乎每个 adddednode 在调用MutationObserver的函数时都可能具有子节点。这样就没有区别了

我真的不想在MutationObserver处理父节点的时候对添加节点(父节点)的每个子节点进行双重检查。大多数情况下,当子节点本身在另一个发生的突变中成为 adddednodes 的一部分时,子节点将由MutationObserver处理,并且开销似乎变得不必要的高。

此外,我考虑了一组节点,它们的子节点必须在MutationObserver调用之外进行分析。如果添加的节点追加到DOM后有子节点,则该节点被添加到Set中。当发生另一个突变并且它的一个子节点是 adddednodes 的一部分时,它的子节点通过使用mutation从集合中删除其父节点。目标——这是父节点(突变)。type必须为childList)。这种方法的问题在于何时检查Set中节点的子节点(以及我可以查询文档的事实)。getElementsByTagname为每个相关的元素类型,而不是维护一个Set,但是时间问题仍然存在)。请记住,它应该尽快保持覆盖响应和适合网站。文档onreadystatechange和添加新的脚本节点到DOM(作为外部JS代码执行时的指示器)的组合可能甚至适用于网站,重新创建其内容的一部分(我正在看你duckduckgo搜索结果页面)。但这似乎是一个变通办法,并不能在100%的情况下解决问题。

那么,有没有其他更有效的方法?或者,如果稍微改变一下,这些方法是否就足够了?非常感谢!

(请尽量避免使用JQuery作为示例代码,谢谢。顺便说一句,我正在使用CEF,所以最好的情况是与Webkit/Blink一起工作的解决方案

EDIT1:网站渲染由CEF内部完成,GUI渲染由c++/OpenGL使用上述Javascript代码获取的信息完成。

看起来您的实际目标是在渲染输出中检测布局变化,而不是(可能不可见的)DOM变化。

在基于壁虎的浏览器上,你可以使用MozAfterPaint来获得改变区域的边界框通知,这是相当精确的,但有一些差距,如视频播放(改变显示的内容,但不改变布局)或异步滚动。

布局也可以通过CSSOM改变,例如通过操纵<style>.sheet.cssRules。CSS动画,已经在评论中提到,是另一种不需要改变就能影响布局的东西。可能还有SMIL动画

因此,单独使用突变观察器可能是不够的。

如果你的覆盖层有一些可利用的几何属性,那么另一种可能性可能是通过文档对视窗的重要部分进行采样。elementFromPoint并计算找到的元素及其子元素的边界框,直到得到所需的元素。通过requestAnimationFrame()调度意味着你应该能够在每一帧上采样当前布局的状态,除非它被其他rAF回调所改变,在你的回调之后运行。

最后,大多数可用的方法似乎都有一些差距,或者需要仔细调整,以避免占用太多的CPU时间。

或者这些方法中的任何一种如果稍微改变就足够了吗?

结合观察到的突变的树遍历和不处理已经访问的节点的WeakSet,可以更仔细地过滤。

  • 已经访问过一个节点并不意味着你可以跳过它的子节点
  • 但是在没有作为突变目标的情况下访问一个孩子应该意味着你可以跳过它
  • removals事件意味着您必须从集合中一个节点一个节点地删除整个子树,或者只是清除集合,因为它们可能被移动到树中的另一个点

MutationRecords似乎是按照变更发生的顺序列出的(可以很容易地验证)。

在运行AnalyzeNode(nodes[i])算法之前,您可以运行AnalyzeChanges(mutations)步骤,以确定发生的所有更改。

例如,如果您看到addedNodes包含相同的节点10次,但您在removedNodes中只看到相同的节点9次,那么您就知道最终的结果是节点最终被添加到DOM中。

当然,它可能比这更复杂,您将不得不检测添加的子树,以及可能随后从这些子树中删除和添加的节点,等等。

最后,一旦你知道净变化是什么,你可以运行AnalyzeNode(nodes[i])

我正在考虑这样做,以观察整个<svg>树,并在WebGL中渲染它(并在发生变化时重新渲染它)。

这可能很棘手,因为假设下面的操作是由一些用户同步进行的(你不知道他/她会做什么),他/她正在操纵DOM:

  • 添加一个子树(在addedNodes中为根节点排队一条记录)
  • 子树的一个子树被删除(排队一条记录)
  • 然后附加在第一个子树之外的其他地方(排队另一个记录,哦,男孩。)
  • 从另一个子树中删除一个元素(队列记录)
  • 并添加回原始子树(队列记录)

最后,您将收到一个MutationRecords列表,其中详细说明了所有这些步骤。

我们可以遍历所有的记录,基本上重现发生了什么,然后我们可以找出最终发生的净变化。

在我们有了这些net更改之后,它就像有了一个记录列表,但它们会更简单,(例如删除然后添加一个节点只是取消它,所以我们并不真正关心一个节点是否被删除然后立即添加,因为最终结果对该节点基本上没有什么)。

人们试图解决检测树木之间变化的问题。

这些解决方案中的许多都与"虚拟dom"answers"dom差分"这两个术语有关,就web而言,它们在谷歌上产生结果。


因此,与其做所有的突变分析(这听起来像一场噩梦,尽管如果你做了,我会说请请将其作为开源发布,以便人们可以受益),我们可以使用一个差分工具来找到突变前的DOM和触发MutationObserver回调时的DOM之间的差异。

举个例子,虚拟世界有一种方法可以得到一个差异,我还没有尝试过。我也不确定如何从DOM树创建VTrees,将它们传递到diff()函数。

啊哈!使用diffDOM可能更容易获得diff。

获取一个diff可能表示从一个树转换到另一个树所需的最简单的变更集,这可能比分析一个突变记录列表要容易得多。值得一试。我可能会把我发现的东西贴回来,当我用<svg> s做的时候…