仅当所有资源均已加载时,MutationObserver回调

时间:2018-10-05 13:48:18

标签: javascript html css mutation-observers

要求

我需要动态更新元素的内容,然后在加载元素及其内容后执行动画。在这种特殊情况下,元素的不透明度为0,加载内容后,我将其不透明度设置为1,并通过CSS过渡使其淡入。元素的内容中包含大量图像,在两个<img>中标签和background-image子元素。非常重要的是,动画只能在所有图像和背景图像都加载后才能开始。否则,淡入效果将被破坏,因为将空的占位符元素设置为动画,其图像内容将在稍后显示,如下所示。

page load

部分解决方案

要确定何时加载元素的内容,我正在使用MutationObserver来观察元素并触发一个回调,该回调会在检测到更改时更改其不透明度。

var page = document.getElementById( "page" );
var observer = new MutationObserver( function(){
    page.style.opacity = "1"; // begin animation on change
} );
var config = { characterData: true,
               attributes: false,
               childList: false,
               subtree: true };

observer.observe( page, config );
page.innerHTML = newContent; // update content

但是,MutationObserver本身并不是满足要求的全面解决方案。正如MDN页上所述:

  

MutationObserver方法observe()将MutationObserver回调配置为开始接收与给定选项匹配的DOM更改通知。

一旦修改了子树,并且加载了所有可能阻止DOM解析的资源,DOM就会触发变化。这不包括图像。因此,MutationObserver会触发回调,而不考虑内容的图像是否已完全加载。

如果将childList参数设置为true,则在回调中,我可以查看每个更改的子代,并将load事件监听器附加到每个<img>,保持跟踪我附上的总数。一旦所有load事件触发,我就会知道每个图像都已加载。

var page = document.getElementById( "page" ),
    total = 0,
    counter = 0;

var callback = function( mutationsList, observer ){
    for( var mutation of mutationsList ) {
        if ( mutation.type == 'childList' ) {
            Array.prototype.forEach.call( mutation.target.children, function ( child ) {
                if ( child.tagName === "IMG" ) {
                    total++; // keep track of total load events
                    child.addEventListener( 'load', function() {
                        counter++; // keep track of how many events have fired
                        if ( counter >= total ) {
                            page.style.opacity = "1";
                        }
                    }, false );
                }
            } );
        }
    }
}

var observer = new MutationObserver( callback );
var config = { characterData: true,
              attributes: false,
              childList: true,
              subtree: true };

observer.observe( page, config );
page.innerHTML = newContent; // update content

但是,有些带有背景图像的元素也必须加载。由于load无法附加到此类元素,因此在检测是否已加载背景图像时,此解决方案没有用。

问题陈述

如何使用MutationObserver确定何时加载动态更新的元素的所有资源?特别是,如何使用它来确定已加载了诸如背景图像之类的隐式依赖资源?

如果用MutationObserver无法做到这一点,那还能怎么实现呢?使用setTimeout延迟动画不是解决方案。

1 个答案:

答案 0 :(得分:0)

在注释中提出了一种解决方案,以检测背景图像的加载。尽管由于我不使用jQuery,确切的实现方式并不适合,但该方法具有足够的可转移性,可以与MutationObserver一起使用。

MutationObserver必须检测到元素及其所有子元素的更改。每个变化的孩子都必须接受调查。如果孩子是图像,则将load事件侦听器附加到该图像,以跟踪我附加的总数。如果孩子不是图像,则检查它是否具有背景图像。如果是这样,则会创建一个新图像,并如上所述附加一个load事件侦听器,并将图像的src设置为子级的backgroundImage的URL。浏览器缓存意味着一旦加载了新图像,背景图像也将加载。

一旦触发的load事件的数量等于附加事件的总数,我就知道所有图像都已加载。

var page = document.getElementById( "page" ),
    total = 0,
    counter = 0;

var callback = function( mutationsList, observer ){
    for( var mutation of mutationsList ) {
        if ( mutation.type == 'childList' ) {
            Array.prototype.forEach.call( mutation.target.children, function ( child ) {
                var style = child.currentStyle || window.getComputedStyle(child, false);

                if ( child.tagName === "IMG" ) {
                    total++; // keep track of total load events
                    child.addEventListener( 'load', loaded, false );

                } else if ( style.backgroundImage != "none" ) {
                    total++; // keep track of total load events
                    var img = new Image();
                    img.addEventListener( 'load', loaded, false );
                    // extract background image url and add as img src
                    img.src = style.backgroundImage.slice(4, -1).replace(/"/g, "");
                }
            } );
        }
    }
    function loaded() {
        counter++; // keep track of how many events have fired
        if ( counter >= total ) {
            page.style.opacity = "1";
        }
    }
}

var observer = new MutationObserver( callback );
var config = { characterData: true,
            attributes: false,
            childList: true,
            subtree: true };

observer.observe( page, config );
page.innerHTML = newContent; // update content

此解决方案似乎有点破烂。需要为每种类型的资源实现一个新案例。例如,如果我还需要等到脚本加载完毕,就必须识别脚本并将load事件侦听器附加到它们,或者如果我必须等待字体加载,则必须编写一个案例为他们。

如果有一个本机函数来确定何时加载所有资源,而不是将事件侦听器附加到所有内容,那将是很好的。服务人员看起来很有趣,但就目前而言,我认为这种骇人听闻的解决方案是最简单,最可靠的。