虽然延迟加载图片和视频可以带来积极且可衡量的性能优势,但这项任务并不容易。如果出错,可能会产生意想不到的后果。因此,请务必牢记以下几点。
注意首屏
使用 JavaScript 对网页上的所有媒体资源进行延迟加载可能会很有诱惑力,但您必须抵挡住这种诱惑。首屏的任何内容均不应进行延迟加载。此类资源应被视为关键资源,因此应正常加载。
延迟加载会将资源加载延迟到 DOM 可交互之后,即脚本加载完毕并开始执行。对于非首屏图片没有问题,但首屏关键资源应使用标准 <img>
元素加载,以便尽快显示。
当然,如今用户通过如此多不同尺寸的屏幕查看网站,首屏线的位置并不是那么明确。在移动设备上,位于首屏的内容可能位于首屏下方。目前并没有完全可靠的建议,无法在每种情况下完美解决这个问题。您需要清点网页的关键资源,并以典型方式加载这些图片。
此外,您可能也不希望将折线作为触发延迟加载的阈值如此严格。对您来说,更理想的做法是在首屏线以下一定距离内建立缓冲区,以便在用户将图片滚动到视口之前就开始加载图片。例如,Intersection Observer API 允许您在创建新的 IntersectionObserver
实例时在选项对象中指定 rootMargin
属性。这样可有效地为元素提供缓冲区,以便在元素进入视口之前触发延迟加载行为:
let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
// lazy-loading image code goes here
}, {
rootMargin: "0px 0px 256px 0px"
});
如果 rootMargin
的值与您为 CSS margin
属性指定的值类似,这是因为该值就是如此!在这种情况下,观察到的元素的下边距(默认为浏览器视口,但可以使用 root
属性将其更改为特定元素)的下边距会加宽 256 像素。这意味着,当图片元素距离视口不超过 256 像素时,回调函数就会执行,并且图片会在用户实际看到图片之前开始加载。
如需在不支持 Intersection Observe 的浏览器中实现同样的效果,请使用滚动事件处理代码,并调整 getBoundingClientRect
检查以包含缓冲区。
布局偏移和占位符
如未使用占位符,延迟加载媒体可能会导致布局偏移。这些更改可能会让用户感到迷惑,并触发代价高昂的 DOM 布局操作,进而消耗系统资源并导致卡顿。至少应考虑使用与目标图片占据相同尺寸的纯色占位符,或使用 LQIP 或 SQIP 等技术,以便在媒体项加载之前提示其内容。
对于 <img>
标记,src
最初应指向一个占位符,直到该属性更新为最终图片网址为止。在 <video>
元素中使用 poster
属性可指向占位图片。此外,请在 <img>
和 <video>
标记上使用 width
和 height
属性。这样可确保从占位符转换为最终图片时,不会在媒体加载时更改元素的渲染大小。
图像解码延迟
在 JavaScript 中加载大型图片并将其拖放到 DOM 中可能会占用主线程,从而导致在解码期间界面短时间无响应。在将图片插入 DOM 之前,使用 decode
方法异步解码图片可以减少此类卡顿,但请注意:此功能尚未在所有地方推出,并且会增加延迟加载逻辑的复杂性。如果您想使用它,则需要先进行检查。下面展示了如何将 Image.decode()
与回退机制搭配使用:
var newImage = new Image();
newImage.src = "my-awesome-image.jpg";
if ("decode" in newImage) {
// Fancy decoding logic
newImage.decode().then(function() {
imageContainer.appendChild(newImage);
});
} else {
// Regular image load
imageContainer.appendChild(newImage);
}
点击此 CodePen 链接,查看与此示例类似的代码的实际应用。如果大多数图片都相当小,这对您来说可能没什么作用,但无疑有助于减少延迟加载大型图片并将其插入 DOM 时出现的卡顿。
内容无法加载时
有时,媒体资源会因为某种原因而加载失败,进而导致出现错误。何时可能会发生这种情况?这视情况而定,但下面是一个假设的场景:您在短时间(例如 5 分钟)内制定了 HTML 缓存政策,用户访问网站或用户在长时间(例如几个小时)上打开过时的标签页,然后返回阅读您的内容。在此过程中的某些时候,会发生重新部署。在此部署期间,映像资源的名称会由于基于哈希的版本控制而发生更改,或者完全被移除。当用户延迟加载图片时,资源不可用,因此会失败。
虽然此类情况比较少见,但如果延迟加载失败,您还是有必要制定备用计划。对于图片,可以采用如下解决方案:
var newImage = new Image();
newImage.src = "my-awesome-image.jpg";
newImage.onerror = function(){
// Decide what to do on error
};
newImage.onload = function(){
// Load the image
};
发生错误时采取何种做法取决于您的应用。例如,您可以将图片占位符区域替换为允许用户尝试重新加载图片的按钮,或者直接在图片占位符区域显示错误消息。
还可能会出现其他情况。无论您采取什么措施,在发生错误时向用户发出信号,并在发生错误时可以给他们采取行动绝非坏事。
JavaScript 可用性
不应假定 JavaScript 始终可用。如果您要延迟加载图片,不妨考虑提供 <noscript>
标记,以便在 JavaScript 不可用时显示图片。最简单的回退示例包括使用 <noscript>
元素在 JavaScript 处于关闭状态时传送图片:
如果 JavaScript 已关闭,用户将同时看到占位符图片和 <noscript>
元素中包含的图片。为了解决此问题,请在 <html>
标记中放置一个 no-js
类,如下所示:
<html class="no-js">
然后,在通过 <link>
标记请求任何样式表之前,在 <head>
中放置一行内嵌脚本,这些标记会从 <html>
元素中移除 no-js
类(如果 JavaScript 已开启):
<script>document.documentElement.classList.remove("no-js");</script>
最后,当 JavaScript 不可用时,使用一些 CSS 隐藏具有 lazy 类的元素:
.no-js .lazy {
display: none;
}
这不会阻止占位符图片加载,但结果却更理想。关闭 JavaScript 的用户不只是看到占位符图片,这比使用占位符和没有任何有意义的图片内容更好。