图像懒加载 (转)
https://css-tricks.com/the-complete-guide-to-lazy-loading-images/
简介:图片是重要的,在导航栏,商品,logo中都需要,很难想象一个网站没有图片是什么样子。然而,图像经常会使网络资源变得庞大。 State of Images report 有具体统计。
因而图片的延迟加载对提高用户体验是很有帮助的。
什么是懒加载?
我们经常将"懒"这个词和什么都不做联系在一起。相似地,懒加载就是只要暂时不需要,就推迟资源加载,直到我们需要的时刻。
为什么需要懒加载?
想象一下,如果一个用户还没有滑动到包含图片的区域,用户不会看到这些图片。那它就不需要立刻加载,因为这些图片根本还没有被使用(看见)。
懒加载优点:
- 性能优化:懒加载减少了需要在页面上预先加载的图像的数量。更少的图像请求意味着需要下载的字节更少。而且,下载的字节数越少,页面的呈现速度就越快。这确保了任何网络上的任何设备都能够更快地下载和处理剩余的资源。因此,从请求到呈现的时间变得更短,页面变得更早可用。
- 成本降低:第二个好处是对一个网站管理员的你。云托管服务,如内容交付网络(CDNs)或web服务器或存储,以传输字节数为基础的成本交付图像(或任何资产)。如果用户从未访问过延迟加载的映像,则它可能永远不会被加载。因此,您可以减少在页面上传递的总字节数,并最终在此过程中为自己节省几分钱。这对于那些立即跳出页面或者只与顶部内容交互的用户来说尤其如此。
懒加载技术
将图像加载到页面有两种常见的方法: img标签
和CSS background-image属性
。我们将首先看看这两个更常见的,img标签
,然后是CSS background-image
。
image标签方式
传统的HTML 图片标签
<img src="/path/to/some/image.jpg" />
第一步:防止图像加载提前。
浏览器使用标记的src属性
来触发图像加载。它不管是HTML文档中的第一个图像还是第1000个图像,如果浏览器获得src属性,它将下载图像,不管它是在当前视图中还是在当前视图之外。
因而为了延迟加载,将图像URL放在src
之外的属性中。假设我们在图像标记的data-src
属性中指定了图像URL。现在src
是空的,浏览器不会触发图像加载:
<img data-src="https://ik.imagekit.io/demo/default-image.jpg" />
现在我们阻止了图像加载,我们需要告诉浏览器何时加载它。否则,它将永远不会发生。因此,我们检测一旦图像(即其占位符)进入视口,我们就触发加载。
有两种方法可以检查图像何时进入视口。让我们用工作代码示例来看看这两个例子。
- 用JavaScript事件触发图像加载
该技术使用浏览器中的scroll
、resize
和 orientationChange
的事件监听器。scroll
事件非常简单明了,因为当滚动发生时,它会监视用户在页面上的位置。resize
和orientationChange
事件同样重要。resize
事件发生在浏览器窗口大小改变时,而orientationChange
则在设备从横向旋转到纵向时触发,反之亦然。
我们可以使用这三个事件来识别屏幕中的变化,并确定在屏幕上可见的图像的数量,并相应地触发它们的加载。
当其中任何一个事件发生时,我们会发现页面上所有被延迟的图像,并从这些图像中检查哪些图像当前位于viewport中。这是通过使用图像的top-offset
、当前文档的顶部位置(top)和窗口高度(window height)来完成的。如果一个图像已经进入了视口,我们从data-src
属性中选择URL并将其移动到src
属性中,图像将因此加载。
注意,我们将要求JavaScript选择包含 lazy
样式的图像。加载图像之后,我们将删除该样式,因为它不再需要触发事件。而且,一旦加载了所有的图像,我们也会删除事件监听器。
<body>
<img src="image1.jpg" />
<img src="image2.jpg" />
<img src="image3.jpg" />
<img class="lazy" data-src="image4.jpg" />
<img class="lazy" data-src="image5.jpg" />
<img class="lazy" data-src="image6.jpg" />
</body>
<script>
document.addEventListener("DOMContentLoaded", function() {
/*与querySelector不同, getElementsByClassName返回的是一个动态的HTMLCollection ,其数据是会动态改变的。
*/var lazyloadImages = document.getElementsByClassName("lazy");
var lazyloadThrottleTimeout;
function lazyload () {
if(lazyloadThrottleTimeout) {
clearTimeout(lazyloadThrottleTimeout);
}
lazyloadThrottleTimeout = setTimeout(function() {
var scrollTop = window.pageYOffset;//当前距离顶部高度
Array.from(lazyloadImages).forEach(function(img) {
if(img.offsetTop < (window.innerHeight + scrollTop)) { //图像进入可视区域时,将src值设为 data-src中的值
img.src = img.dataset.src;
img.classList.remove('lazy');
}
});
if(lazyloadImages.length == 0) {
//当lazyloadImages没有时,清除监听事件
document.removeEventListener("scroll", lazyload);
window.removeEventListener("resize", lazyload);
window.removeEventListener("orientationChange", lazyload);
}
}, 20);
}
document.addEventListener("scroll", lazyload);
window.addEventListener("resize", lazyload);
window.addEventListener("orientationChange", lazyload);
});
</script>
注意:本例中的前三个图像是预先加载的。URL直接出现在src属性中,而不是data-src属性中。这对于良好的用户体验至关重要。由于这些图像位于页面的顶部,所以应该尽快将它们显示出来。不需要等待JavaScript来加载它们。
DOMContentLoaded
与load
当初始的 HTML 文档被完全加载和解析完成之后,
DOMContentLoaded
事件被触发,而无需等待样式表、图像和子框架的完成加载。另一个不同的事件load
应该仅用于检测一个完全加载的页面。
官网描述:
Intersection Observer API提供了一种异步观察目标元素与祖先元素或顶级文档viewport的交集中的变化的方法。
一直以来,检测元素的可视状态或者两个元素的相对可视状态都不是件容易事,毕竟大部分解决办法并非完全可靠,也极易拖慢整个网站的性能。然而,随着网页发展,对上述检测的需求也随之增加了。多种情况下都需要用到元素交集变化的信息,比如:
- 当页面滚动时,懒加载图片或其他内容。
- 实现“可无限滚动”网站,也就是当用户滚动网页时直接加载更多内容,无需翻页。
- 为计算广告收益,检测其广告元素的曝光情况。
- 根据用户是否已滚动到相应区域来灵活开始执行任务或动画。
概念及基本用法:
Intersection Observer API 允许你配置一个回调函数,每当目标(target)元素与设备视窗或者其他指定元素发生交集的时候执行。设备视窗或者其他元素我们称它为根元素或根(root)。
/*
第一步:
创建一个 IntersectionObserver对象,并传入相应参数和回调用函数,该回调函数将会在目标(target)元素和根(root)元素的交集大小超过阈值(threshold)规定的大小时候被执行。
*/
var options = {
//指定根(root)元素,用于检查目标的可见性。必须是目标元素的父级元素。默认为浏览器视窗。
root: document.querySelector('#scrollArea'),
//root元素的外边距。类似于css中的 margin 属性.使用该属性可以控制root元素每一边的收缩或者扩张。
rootMargin: '0px',
/* 可以是单一的number也可以是number数组
如果你只是想要探测当target元素的在root元素中的可见性超过50%的时候,你可以指定该属性值为0.5。如果你想要target元素在root元素的可见程度每多25%就执行一次回调,那么你可以指定一个数组[0, 0.25, 0.5, 0.75, 1]。默认值是0(意味着只要有一个target像素出现在root元素中,回调函数将会被执行)。
*/
threshold: 1.0
}
var observer = new IntersectionObserver(callback, options);
/*
第二步
为每个观察者配置一个目标
*/
var target = document.querySelector('#listItem');
observer.observe(target);
//只要目标满足为IntersectionObserver指定的阈值,就会调用回调。
function callback(entries, observer) {
entries.forEach(entry => {
// Each entry describes an intersection change for one observed
// target element:
// entry.boundingClientRect
// entry.intersectionRatio
// entry.intersectionRect 用来描述根和目标元素的相交区域.
// entry.isIntersecting 返回一个布尔值, 如果目标元素与交叉区域观察者对象(intersection observer) 的根相交,则返回 true
// entry.rootBounds
// entry.target 当前与根出现相交区域改变的元素
// entry.time 返回一个记录从 IntersectionObserver 的时间原点(time origin)到交叉被触发的时间的时间戳
});
};
因而对于该例子中,只需修改其js
document.addEventListener("DOMContentLoaded", function() {
var lazyloadImages;
if ("IntersectionObserver" in window) {
lazyloadImages = document.querySelectorAll(".lazy");
var imageObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
var image = entry.target;
image.src = image.dataset.src;
image.classList.remove("lazy");
imageObserver.unobserve(image);
}
});
});
lazyloadImages.forEach(function(image) {
imageObserver.observe(image);
});
} else {
...退化操作,如果浏览器还未实现 IntersectionObserver,使用第1个方法
})
background-image方式
CSS背景图像不像图像标签那样直观。要加载它们,浏览器需要构建DOM树和CSSOM树,以确定CSS样式是否适用于当前文档中的DOM节点。如果指定背景图像的CSS规则不适用于文档中的元素,则浏览器不加载背景图像。如果CSS规则适用于当前文档中的元素,那么浏览器将加载图像。
<style>
#container {
font-size: 20px;
line-height: 30px;
max-width: 600px;
}
#bg-image.lazy {
background-image: none;
background-color: #F1F1FA;
}
#bg-image {
background-image: url("https://ik.imagekit.io/demo/img/image10.jpeg?tr=w-600,h-400");
max-width: 600px;
height: 400px;
}
</style>
<body>
......一些元素
<div id="bg-image" class="lazy"></div>
.....
</body>
<script>
逻辑也是基本一样,可以用 js事件也可以用IntersectionObserver API
当图片进入可视区域时,将其lazy 样式去除。这样css规则匹配,则开始加载图片
</script>
更好的用户体验
延迟加载带来了巨大的性能优势。对于在页面上加载数百个产品图像的电子商务公司来说,延迟加载可以在初始页面加载方面提供显著的改进,同时降低带宽消耗。
然而,许多公司并不选择延迟加载,因为他们认为延迟加载不利于提供良好的用户体验(例如,初始占位符很难看,加载时间很慢等等)。
在本节中,我们将尝试解决有关延迟加载图像的用户体验的一些问题。
技巧1:使用正确的占位
占位符出现在容器中,直到实际的图像被加载。通常,我们看到开发人员为图像使用纯色占位符,或者为所有图像使用单个图像占位符。
到目前为止我们看到的例子都使用了类似的方法:一个具有纯浅灰色背景的框。然而,我们可以做得更好,提供一个更令人愉快的用户体验。下面是为我们的图片使用更好的占位符的两个例子。
主体颜色占位符
我们不使用固定的颜色作为图像占位符,而是从原始图像中找到主色并将其用作占位符。
<!-- Original image at 400x300 -->
<img src="https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300" alt="original image" />
<!-- Dominant color image with same dimensions -->
<img src="https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-1,h-1:w-400,h-300" alt="dominant color placeholder" />
也可采用低质量图片作为占位符
<!-- Original image at 400x300 -->
<img src="https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300" alt="original image" />
<!-- Low quality image placeholder with same dimensions -->
<img src="https://ik.imagekit.io/demo/img/image4.jpeg?tr=w-400,h-300,bl-30,q-50" alt="dominant color placeholder" />
这种通过参数对图片进行操作,应该是一些图床具有的功能,比如ImageKit
技巧2:增加加载图像的缓冲时间
当我们讨论触发图像加载的不同方法时,我们检查了图像进入视口的时间点,即当图像占位符的上边缘与视口的下边缘重合时触发图像加载。
这样做的问题是,用户可能会非常快地滚动页面,而图像需要一些时间才能加载并显示在屏幕上。再加上可能会进一步延迟加载的节流,用户最终可能要多等几毫秒才能看到图像。对于用户体验不是很好!
不是在图像刚进入视口时加载它,而是在它进入视口前500px时加载它。这提供了加载触发器和视图中实际条目之间的额外时间,以便加载图像。
/*使用Intersection Observer API
设置rootMargin 来扩大有效边界
*/
let options = {
root: document.querySelector("#container"),
rootMargin: "0px 0px 500px 0px"
}
/*
也可通过距离判断
*/
offsetTop < window.innerHeight + scrollTop + 500
技巧3:避免回流
当没有图像时,浏览器不知道它将占用的大小。如果我们不使用CSS指定它,那么封闭的容器将没有维度,也就是说它将被读取为0x0像素。
当图像加载时,浏览器将把它放到屏幕上,并重新流动内容以适应它。这种布局上的突然变化会导致其他元素四处移动,这被称为内容回流或移动(重排与重绘)。
也就是说可以通过指定封闭容器的高度和/或宽度来避免,这样浏览器就可以用已知的高度和宽度来绘制图像容器。稍后,当图像加载时,由于容器大小已经指定,且图像完全适合该大小,所以容器周围的其余内容不会移动。
技巧4:避免所有图像都懒加载
这是开发人员经常犯的一个错误,因为人们很容易认为延迟加载图像总是好的。但是,就像生活本身一样,好东西太多也是有可能的。延迟加载可能会减少初始页面加载,但如果一些图像在不应该延迟的情况下延迟,也可能会导致糟糕的用户体验。
我们可以遵循一些一般原则来确定哪些图像应该延迟加载。例如,在viewport中出现的任何图像,或者在网页的开头,都不应该延迟加载。这适用于任何页眉图像、营销横幅、徽标,或任何用户最初在页面上看到的东西。另外,请记住,移动设备和桌面设备将有不同的屏幕大小,因此最初在屏幕上可以看到不同数量的图像。你需要考虑正在使用的设备并决定使用哪种资源。
另一个例子是,任何在初始加载时稍微偏离视口的图像都不应该被延迟加载。这是根据前面讨论过的原则进行的。我们说任何500px的图像或者从视图底部的单个滚动条区域都可以被预加载。
另一个例子是如果页面很短。它可能只是一个滚动或两个滚动距离,或可能有不到五个图像以外的视口。在这些情况下,可以完全不进行延迟加载。就性能而言,它不会给最终用户带来任何显著的好处,而且您为启用延迟加载而在页面上加载的额外JavaScript将抵消您从中获得的任何潜在收益。