MutationObserver 仅在所有资源加载完毕时回调

问题描述 投票:0回答:1

要求

我需要动态更新元素的内容,然后在加载元素及其内容后执行动画。在这种特殊情况下,元素的不透明度为 0,加载内容后,我将其不透明度设置为 1,并使用 CSS 过渡使其淡入。该元素的内容在

<img>
标签和作为子元素的
background-image
。非常重要的是,只有在所有图像和背景图像加载完毕后才允许开始动画。否则,淡入效果会被破坏,因为空占位符元素会被动画化,其图像内容稍后会出现,如下所示。

部分解决方案

为了确定何时加载元素的内容,我使用

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
s 将触发回调,而不管内容的图像是否已完全加载。

如果我将

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
来确定何时加载动态更新元素的 all 资源?特别是,我如何使用它来确定隐式依赖的资源(例如背景图像)已加载?

如果用

MutationObserver
无法实现这一点,那么还有什么办法可以实现呢?使用
setTimeout
来延迟动画并不是解决方案。

javascript html css mutation-observers
1个回答
3
投票

评论中提出了一个解决方案来检测背景图像加载。虽然确切的实现并不合适,因为我没有使用 jQuery,但该方法足以与

MutationObserver
一起使用。

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

一旦触发的

load
事件数等于附加事件总数,我就知道所有图像都已加载。

const page = document.getElementById( "page" )
let total = 0
let counter = 0

const mutCallback = ( mutationsList, observer ) => {
    for( const mutation of mutationsList ) {
        if ( mutation.type === 'childList' ) {
            for ( const child of mutation.target.children ) {
                const 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
                    const 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 ) {
            // All images have loaded so can do final logic here
            page.style.opacity = "1";
        }
    }
}

const observer = new MutationObserver( mutCallback )
const config = {
    characterData: true,
    attributes: false,
    childList: true,
    subtree: true
}

observer.observe( page, config );

// update content, and now it will be observed
page.innerHTML = newContent;

这个解决方案似乎有点黑客。需要为每种类型的资源实施一个新案例。例如,如果我还需要等待脚本加载,我将必须识别脚本并将

load
事件侦听器附加到它们,或者如果我必须等待字体加载,我将必须为它们编写一个案例.

如果有一个本机函数来确定所有资源何时加载,而不是将事件侦听器附加到所有内容,那就太好了。 Service Worker 看起来很有趣,但目前我认为 hackish 解决方案是最简单、最可靠的。

© www.soinside.com 2019 - 2024. All rights reserved.