所属分类:web前端开发
我之前写过一篇教程,讨论如何使用 HTML、CSS 或 JavaScript 在网页上预加载图像。我们费尽心思预加载图像的原因是为了向用户提供更好的浏览体验,这样他们就不必等待图像加载。
改善用户体验的相同理念也适用于延迟加载图像。当我们浏览网站时,图像是影响页面重量的最大因素之一。以最佳方式加载它们可以提高性能并节省带宽。
在本教程中,我们将了解延迟加载图像的不同方法。
我们将通过了解为什么您应该为延迟加载图像而烦恼来开始本教程。假设您正在为一位摄影师构建一个作品集网站,他们在一个页面上展示了所有最好的图像。
并不是每个人都会滚动到页面底部来查看所有图像。但是,图像仍会由用户的浏览器下载。这在下面的 CodePen 演示中很明显:
即使您没有滚动超过上面演示中的第一个图像,您也会看到浏览器已加载所有图像。以下浏览器开发人员工具中网络选项卡的屏幕截图显示发出了 38 个请求,传输了大约 2.5MB 的数据。浏览器总共下载了 19 个图像,重定向使请求数量增加了一倍。
我们现在将尝试改进或优化图像加载以节省资源。
延迟加载图像的最简单方法涉及使用 loading
属性。所有现代浏览器都支持图像上的 loading
属性,该属性可用于指示浏览器防止加载不在屏幕上的图像,并且仅在用户滚动到足够近以使其可见时才开始加载它们。 p>
loading
属性可以接受两个可能的值。第一个值是 eager
,它告诉浏览器立即加载图像,即使它当前不在视口内。这是浏览器的默认行为。
第二个值是 lazy
,它告诉浏览器推迟加载图像,直到它到达距视口的特定距离。该距离由浏览器定义。将 loading
属性的值设置为 lazy
可能会为客户端节省带宽。
请务必记住,浏览器仅延迟加载当前在视口中不可见的图像。通常,网页上的图像与其他文本一起放置,将它们推到视口之外。在这种情况下,您不需要执行任何特殊操作来确保图像延迟加载。
但是,请考虑本教程中的示例,其中网页仅包含图像。在这种情况下,如果您希望延迟加载图像,那么提及图像的大小就变得很重要。否则,所有图像最初的宽度和高度都将为零。这将使浏览器认为所有图像在视口中都可见,并且会立即加载所有图像。
在这种情况下,显式指定图像宽度和高度会将某些图像推出视口。您可以使用 width
和 height
HTML 属性或在 CSS 中自由指定图像尺寸。
这是延迟加载图像的标记:
<img loading="lazy" src="https://picsum.photos/id/628/1080/1080" width="600" height="600">
正如我之前所说,您还可以在 CSS 中指定图像尺寸,并从标记中删除 width
和 height
属性:
<img loading="lazy" src="https://picsum.photos/id/628/1080/1080">
相应的 CSS 为:
img { width: 600px; height: 600px; }
以下 CodePen 演示展示了延迟加载的实际效果:
我的浏览器开发者工具中的网络选项卡显示,这次只下载了四张图像,传输的数据量约为 450 kB。页面上共有 19 张图片,这意味着另外 15 张图片将被延迟下载。就带宽而言,这意味着节省了约 80%。
这里要记住的一件重要事情是,即使不涉及脚本,图像的延迟加载也仅在启用 JavaScript 时才有效。这样做是为了防止通过策略性放置的图像来跟踪用户的滚动位置。
浏览器如何确定何时应该下载应该延迟加载的图像?触发延迟加载图像下载的确切条件因浏览器而异。然而,两个主要因素似乎是距视口的距离和网络速度。
如果您想精确控制延迟加载图像的下载时间,则必须使用 JavaScript。
现在我们将学习如何使用 JavaScript 延迟加载图像。这将使我们能够更好地控制整个过程。如果您认为默认的延迟加载不够激进,您可以使用 Intersection Observer API 创建自己的延迟加载脚本。
在编写任何 JavaScript 之前,我们需要对标记进行一些更改:
<img class="lazy-load" data-src="https://picsum.photos/id/628/1080/1080">
我们的 img
标签现在将包含一个名为 lazy-load
的类,以帮助我们识别哪些图像需要延迟加载。 img
标签将使用 data-src
属性来跟踪图像路径,而不是 src
属性。这会阻止图像立即开始下载。
Intersection Observer API 允许我们检测目标元素是否与其任何祖先元素或文档的视口相交。我们将使用 IntersectionObserver()
构造函数来创建 IntersectionObserver
对象。该构造函数接受回调函数作为其第一个参数,并接受一个用于自定义观察者行为的可选对象作为第二个参数。
我们传递给构造函数的回调函数接收两个参数。第一个是相交元素的数组,第二个是观察者本身。自定义选项允许您指定要检查交集的根元素、向根元素添加额外偏移值的根边距以及确定浏览器何时开始报告交集的阈值。
这是我们的交叉点观察者对象的代码:
function preload_image(img) { img.src = img.dataset.src; console.log(`Loading ${img.src}`); } const config_opts = { rootMargin: '200px 200px 200px 200px' }; let observer = new IntersectionObserver(function(entries, self) { for(entry of entries) { if(entry.isIntersecting) { let elem = entry.target; preload_image(elem); self.unobserve(elem); } } }, config_opts);
我在根元素(本例中为视口)的所有边上提供了 200 像素的边距。只要任何图像位于视口 200 像素范围内,我们的交叉点观察器就会激活。默认情况下,阈值设置为 0。值为零意味着只要图像的一小部分位于我们指定的范围内,就会调用 preload_image()
函数。 unobserve()
方法告诉浏览器停止观察该特定图像以进行进一步的交叉。
preload_image()
函数获取图像的 data-src
属性的值,并将其应用于 src
属性。这会触发我们图像的下载。
我们现在需要做的就是查询文档中的所有图像,然后告诉观察者观察它们是否有交集。这是为我们实现这一目标的代码。
let images = document.querySelectorAll('img.lazy-load'); for(image of images) { observer.observe(image); }
您是否注意到我们正在使用 img.lazy-load
选择器来查询我们的图像?这个类可以帮助我们轻松识别所有想要延迟加载的图像。没有此类的图像将正常加载。
这是一个 CodePen 演示,用于查看我们的图像是否确实延迟加载。
这次,我的浏览器开发者工具中的网络选项卡显示,之前只下载了两张图片,总共传输的数据量约为 192 kB。与原始演示相比,我们的带宽节省现已高达 92%。
我承认我已经让交叉观察者变得非常激进,只加载非常接近视口的图像。然而,这就是您自己实现该功能的美妙之处。
延迟加载图像对每个人来说都是双赢的。它将减少服务器的负载,同时节省用户的带宽。请记住,数据,尤其是移动数据,在世界上的某些地方非常昂贵。
现在浏览器原生支持延迟加载图像,只需对标记进行微小更改即可充分利用该功能。浏览器还足够智能,可以根据网络速度和图像位置确定延迟加载图像的理想时间。您还可以使用 Intersection Observer API 相对轻松地自行实现该功能。
这里要记住的一件重要事情是,如果用户端禁用 JavaScript,这些技术都将不起作用。