MutationObserver 仅在所有资源已加载时回调
MutationObserver callback only when all resources have loaded
要求
我需要动态更新元素的内容,然后在加载元素及其内容后执行动画。在这种特殊情况下,元素的不透明度为 0,加载内容后,我将其不透明度设置为 1,并使用 CSS 过渡使其淡入。元素的内容有大量图像,在 <img>
标签和子元素的 background-image
s。非常重要的是,只有在所有图像和背景图像都已加载后才允许开始动画。否则淡入效果会被破坏,因为空的占位符元素会被动画化,其图像内容会在瞬间出现,如下所示。
部分解决方案
为了确定何时加载元素的内容,我使用 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 页面所述:
The MutationObserver method observe() configures the MutationObserver callback to begin receiving notifications of changes to the DOM that match the given options.
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
确定何时加载动态更新元素的 所有 资源?特别是,我如何使用它来确定隐式依赖资源(如背景图像)是否已加载?
如果 MutationObserver
s 无法做到这一点,还有什么办法可以做到?使用setTimeout
延迟动画不是解决方案。
评论中提出了检测背景图片加载的解决方案。虽然我没有使用 jQuery,因此确切的实现并不合适,但该方法的可转移性足以与 MutationObserver
.
一起使用
MutationObserver
必须检测元素及其所有 children 的变化。每一个 child 的变化都必须进行调查。如果 child 是一个图像,那么 load
事件侦听器将附加到它,跟踪我附加的总数。如果 child 不是图像,则检查它是否有背景图像。如果是,则创建一个新图像,如上附加一个 load
事件侦听器,并将图像的 src
设置为 child 的 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
这个解决方案似乎有点 hack。需要为每种类型的资源实施一个新案例。例如,如果我还需要等到脚本加载完毕,我将不得不识别脚本并将 load
事件侦听器附加到它们,或者如果我必须等待字体加载,我将不得不编写一个案例为了他们。
如果有一个本机函数来确定所有资源何时加载,而不是将事件侦听器附加到所有内容,那就太好了。服务工作者看起来很有趣,但目前我认为 hackish 解决方案是最简单和最可靠的。
要求
我需要动态更新元素的内容,然后在加载元素及其内容后执行动画。在这种特殊情况下,元素的不透明度为 0,加载内容后,我将其不透明度设置为 1,并使用 CSS 过渡使其淡入。元素的内容有大量图像,在 <img>
标签和子元素的 background-image
s。非常重要的是,只有在所有图像和背景图像都已加载后才允许开始动画。否则淡入效果会被破坏,因为空的占位符元素会被动画化,其图像内容会在瞬间出现,如下所示。
部分解决方案
为了确定何时加载元素的内容,我使用 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 页面所述:
The MutationObserver method observe() configures the MutationObserver callback to begin receiving notifications of changes to the DOM that match the given options.
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
确定何时加载动态更新元素的 所有 资源?特别是,我如何使用它来确定隐式依赖资源(如背景图像)是否已加载?
如果 MutationObserver
s 无法做到这一点,还有什么办法可以做到?使用setTimeout
延迟动画不是解决方案。
评论中提出了检测背景图片加载的解决方案。虽然我没有使用 jQuery,因此确切的实现并不合适,但该方法的可转移性足以与 MutationObserver
.
MutationObserver
必须检测元素及其所有 children 的变化。每一个 child 的变化都必须进行调查。如果 child 是一个图像,那么 load
事件侦听器将附加到它,跟踪我附加的总数。如果 child 不是图像,则检查它是否有背景图像。如果是,则创建一个新图像,如上附加一个 load
事件侦听器,并将图像的 src
设置为 child 的 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
这个解决方案似乎有点 hack。需要为每种类型的资源实施一个新案例。例如,如果我还需要等到脚本加载完毕,我将不得不识别脚本并将 load
事件侦听器附加到它们,或者如果我必须等待字体加载,我将不得不编写一个案例为了他们。
如果有一个本机函数来确定所有资源何时加载,而不是将事件侦听器附加到所有内容,那就太好了。服务工作者看起来很有趣,但目前我认为 hackish 解决方案是最简单和最可靠的。