我需要动态更新元素的内容,然后在加载元素及其内容后执行动画。在这种特殊情况下,元素的不透明度为 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
来延迟动画并不是解决方案。
评论中提出了一个解决方案来检测背景图像加载。虽然确切的实现并不合适,因为我没有使用 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 解决方案是最简单、最可靠的。