谷歌的“加速移动页面”(AMP)项目最近帮助影响网站变得更快。凭借良好的技术和强大的内容分发网络,Google 直接让 AMP 增强的网站变得更快。然而,AMP 也间接地发挥了作用,鼓励我们研究 AMP 包含的优化和最佳实践。即使您不打算让您的网站符合 AMP 标准,也可以将 AMP 理解为优化非 AMP 网站的待办事项列表。
此列表中的一项优化是一种称为“延迟加载”的技术,我们在最近关于使用 AMP 的 <amp-img> 自定义元素的文章中看到了这种技术 。使用这种技术,当访问者第一次到达页面时,只会加载视口中或视口附近的图像。当访问者向下滚动时,其余部分被触发加载。
延迟加载可以让访问者更快地开始与内容互动,增强的加载速度可以提高您的搜索引擎排名。页面上的图像越多,您获得的速度提升就越大。
在本教程中,我们将了解如何使用名为Layzr.js的脚本在非 AMP 网站上部署延迟加载。我们将尽可能地复制 AMP<amp-img>元素的功能,但我们还将使用 Layzr 特有的一些功能。
让我们开始吧!
1.基本设置
作为文章“ AMP 项目:它会让您的网站更快吗? ”的一部分,我创建了一个包含五个图像的基本布局。为了使您能够在使用 AMP 和自己部署延迟加载之间进行比较,我们将重新创建相同的五个图像布局。我将在本教程后面向您展示如何运行各种负载速度测试。
在本教程所附的源文件中,您将找到布局的 AMP 版本,以及您将在此处制作的完整版本。两者都包括在内,以帮助您确定哪种方法最适合您。
当我们逐步完成所有操作时,我建议使用 chrome 开发人员工具 ( F12 )测试您的工作,同时打开Network选项卡,选中Disable Cache ,并将限制设置为Regular 3G。这模拟了平均移动连接,实时显示每个图像加载的图表,并将帮助您清楚地了解延迟加载的运行情况。
当您刷新页面进行测试时,按住重新加载按钮,这将显示一个带有不同选项的下拉列表。选择Empty Cache 和 Hard Reload以完全模拟第一次到达您的站点的访问者。
创建html外壳
让我们从了解基础开始。首先,创建一个文件夹来存放您的项目,并在其中创建一个名为index.html的文件。
打开它进行编辑并添加以下代码:
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Layzr.js Lazy Loading</title> <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1"> <style> body { margin: 0; } img { display: block; margin: 0 auto; } </style> </head> <body> <h1>Welcome to the lazy loaded web</h1> </body> </html>
使用上面的代码,我们只是获得了一个 HTML 外壳,并包含一些css以确保页面body和图像周围没有任何意外的空白。
我们还包括margin: 0 auto;因此我们稍后添加的图像将居中。
加载Layzr
layzr.js 脚本有两个方便的 CDN 源可供您加载——我们将使用来自 Cloudfare 的一个。
将此代码添加到您的 html 中,在结束</body>标记之前。
<script src="https://cdnjs.cloudflare.com/ajax/libs/layzr.js/2.0.2/layzr.min.js " ></script>
如果您不想从 CDN 加载脚本,您可以下载它并按照以下位置的简要说明进行操作: https ://github.com/callmecavs/layzr.js#download
实例化 Layzr
现在我们已经加载了 Layzr,我们需要让它在页面加载时执行。为此,请在script您刚刚在上一节中插入的标签之后添加此代码:
<script>const instance = Layzr() document.addeventListener('domContentLoaded', function(event){ instance.update().check().handlers(true) }) </script>
此代码首先创建一个用于包含 Layzr 的实例,然后一旦页面的 DOM 内容加载完毕,它就会使用该实例来激活 Layzr 的功能。
到目前为止,您的整体代码现在应该如下所示:
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Layzr.js Lazy Loading</title> <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1"> <style> body { margin: 0; } img { display: block; margin: 0 auto; } </style> </head> <body> <h1>Welcome to the lazy loaded web</h1> <script src="https://cdnjs.cloudflare.com/ajax/libs/layzr.js/2.0.2/layzr.min.js"></script> <script> const instance = Layzr() document.addEventListener('DOMContentLoaded', function(event){ instance.update().check().handlers(true) }) </script> </body> </html>
2.添加图片(普通分辨率)
加载 Layzr 并准备就绪后,我们可以开始添加一些图像,让它发挥它的魔力。您可以使用任何您想要的图像,但是如果您想使用您在以下步骤中看到的确切代码示例,您可以下载本教程所附的源文件。在那里,您会找到一个图像文件夹,您可以将其复制并粘贴到您自己的项目中。
要在使用 Layzr 时添加图像,您将使用常规img元素,而不是使用src您将使用的属性,data-normal 如下所示:
<img data-normal="images/vulture.jpg" alt="Vulture">
确保图像具有高度
为了让任何延迟加载脚本工作,它需要知道站点上所有图像的高度,以便它可以决定哪些需要加载(因为它们在视口中或靠近它),哪些应该等待.
然而,棘手的部分是图像在完全加载到页面之前实际上没有任何高度。这意味着如果我们想要延迟加载工作,我们需要一种方法在加载图像之前提供有关图像高度的页面信息。
我们将介绍两种方法来实现这一点,一种用于固定大小的图像,另一种用于响应式。通过使图像大小固定来赋予图像高度是最简单的方法,因为您只需要添加 height和width属性。
<img data-normal="images/vulture.jpg" alt="Vulture" height="640" width="960">
img现在继续并在脚本标签上方添加 元素,使用data-normal属性,并为要加载的每个图像包括heightand 。width
<img data-normal="images/vulture.jpg" alt="Vulture" height="640" width="960"> <img data-normal="images/beach.jpg" alt="Beach" height="640" width="960"> <img data-normal="images/bear.jpg" alt="Bear" height="640" width="960"> <img data-normal="images/sky.jpg" alt="Sky" height="540" width="960"> <img data-normal="images/bike.jpg" alt="Bike" height="540" width="960">
这种固定大小的方法将允许延迟加载工作,但是它会阻止图像响应,这并不理想。稍后我们将介绍如何同时赋予图像高度和响应能力。
3.设置加载阈值
默认情况下,Layzr 只会引入加载时可见的图像。但是,如果还预加载了下一行(就在视口之外)的图像,访问者将获得更流畅的体验。
threshold通过在实例化脚本时设置一个名为的选项来做到这一点。它的工作方式是您将提供一个代表视口高度百分比的值。如果您将值设置为 100,这将表示 100% 的视口高度,例如 1200 像素。在这种情况下,也将加载视口 1200 像素内的任何屏幕外内容。
例如,如果您有两个大图像,其中一个被推到视口之外,并且您的阈值设置为 100,则两个图像都会加载:
要设置阈值,请在代码中替换此行:
const instance = Layzr()
...有了这个:
const instance = Layzr({ threshold: 100 })
您可以将此值更改为最适合您创建的站点的值。有趣的是,AMP 的延迟加载阈值似乎大致相当于 200。
4.添加 Retina/HiDPI 图像
Layzr 的一大优点是它可以非常直接地为高分辨率设备添加图像。您需要做的就是包含属性data-retina。例如:
<img data-normal="images/vulture.jpg" data-retina="images/vulture@2x.jpg" alt="Vulture" height="640" width="960">
更新 imgHTML 中的所有元素以包含视网膜图像,如下所示:
<img data-normal="images/vulture.jpg" data-retina="images/vulture@2x.jpg" alt="Vulture" height="640" width="960"> <img data-normal="images/beach.jpg" data-retina="images/beach@2x.jpg" alt="Beach" height="640" width="960"> <img data-normal="images/bear.jpg" data-retina="images/bear@2x.jpg" alt="Bear" height="640" width="960"> <img data-normal="images/sky.jpg" data-retina="images/sky@2x.jpg" alt="Sky" height="540" width="960"> <img data-normal="images/bike.jpg" data-retina="images/bike@2x.jpg" alt="Bike" height="540" width="960">
5.响应式图像占位符和回流预防
使延迟加载的图像具有响应性可能是一个棘手的问题。正如我们之前提到的,为了确定何时加载图像,Layzr 首先需要知道它们的高度。因为响应式图像一直在改变它们的尺寸,所以它们的高度是不可预测的。
除此之外,我们还希望在我们的页面布局中有一些东西来防止回流。回流是当图像完成加载并从没有大小到突然占用布局中的空间时发生的情况,导致它周围的所有东西都移动。对于试图专注于您的内容的访问者来说,这可能会非常令人沮丧,只是让它在他们的页面上跳转。
我们可以通过在页面中为每个图像设置正确大小的响应式占位符来解决这两个问题。占位符将确保页面布局不需要重排,并且还将为 Layzr 提供可使用的高度。我们的方法将基于 Thierry Koblentz 的这篇“A List Apart”文章“为视频创建内在比率”中的巧妙技术。
唯一的附带条件是您需要提前知道您发布的每张图片的纵横比,因为 CSS 会根据指定的纵横比调整图片的大小。
添加纵横比包装器
我们要做的第一件事是div在我们的第一个图像周围添加一个包装器——这div将成为我们的占位符。我们将div使用 CSS 调整自身的大小,然后在其中设置图像以水平和垂直填充它。
我们将给div一个类名来表示它将包含的图像的纵横比。在我们的示例中,第一张图像是 960 像素宽 x 640 像素高,所以让我们弄清楚它的纵横比是多少。
640(我们的高度)是 960(我们的宽度)的三分之二,这意味着对于每 2 个单位的高度,图像有 3 个单位的宽度。Aspect rat ios通常表示为width:height,如众所周知的情况一样 16:9。我们的第一个示例图像的比例是3:2。
为了表示这个纵横比,我们将给我们的包装器div类名ratio_3_2。
<div class="ratio_3_2"> <img data-normal="images/vulture.jpg" data-retina="images/vulture@2x.jpg" alt="Vulture" height="640" width="960"> </div>
添加标准纵横比样式
现在我们将添加 CSS 以使这一切正常工作。
在index.html<style></style>文件头部的现有标签之间,添加以下代码:
div[class^="ratio_"]{ position: relative; width: 100%; }
这个选择器会选择我们的ratio_3_2类,但它也会选择以 . 开头的任何其他类ratio_。这意味着我们可以稍后创建更多类以适应不同的纵横比。
在这种样式中,我们确保我们的包装器总是拉伸到其父宽度的 100%。我们还将它设置为,position: relative;因为这绝对会将图像定位在其中 - 您稍后会看到原因。
给出纵横比包装高度
现在我们将只添加特定于我们的ratio_3_2类的代码:
.ratio_3_2 { /*padding-top: calc( 100% * (2 / 3) );*/ padding-top: 66.666667%; }
那里的padding-top价值使我们能够将包装器保持div在我们想要的纵横比。无论div.
要确定要放在这里的百分比,请弄清楚以宽度百分比表示时纵横比的高度是多少。您可以通过以下计算做到这一点:
100% * (height / width)
对于我们的 3:2 比例来说:100% * (2 / 3) = 66.666667%
您可以提前计算所需纵横比的正确百分比,或者如果您愿意,可以使用calc()上面示例中注释掉的 CSS 函数:
padding-top: calc( 100% * (2 / 3) );
用图像填充纵横比包装器
无论视口宽度如何,我们的纵横比包装器现在都将保持所需的尺寸。所以现在我们要做的就是让其中包含的图像填满包装器,从而继承它的尺寸。
我们将通过绝对定位嵌套在ratio_分类包装器 中的任何图像来做到这一点div,将其放置在包装器的左上角,然后将其拉伸到 100% 的高度和宽度,如下所示:
div[class^="ratio_"] > img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
检查您的第一张图像,您现在应该看到它延伸到视口的宽度,但在您调整大小时会缩小以适应,同时保持其纵横比。
添加额外的纵横比
您可能拥有各种不同纵横比的图像,并且您希望能够容纳它们。在本教程中我们使用的示例图像中,前三个具有 3:2 的纵横比,但第四个和第五个是 16:9。
为了解决这个问题,添加一个根据纵横比命名的新类,即ratio_16_9,具有相应的 padding-top值:
.ratio_16_9 { /*padding-top: calc( 100% * (9 / 16) );*/ padding-top: 56.25%; }
继续并div在所有其余图像周围添加纵横比包装器,根据它们的大小为每个图像使用适当的类。您还可以从图像中删除height和width属性,因为它们现在都将被我们的 CSS 覆盖。
<div class="ratio_3_2"> <img data-normal="images/vulture.jpg" data-retina="images/vulture@2x.jpg" alt="Vulture"> </div> <div class="ratio_3_2"> <img data-normal="images/beach.jpg" data-retina="images/beach@2x.jpg" alt="Beach"> </div> <div class="ratio_3_2"> <img data-normal="images/bear.jpg" data-retina="images/bear@2x.jpg" alt="Bear"> </div> <div class="ratio_16_9"> <img data-normal="images/sky.jpg" data-retina="images/sky@2x.jpg" alt="Sky"> </div> <div class="ratio_16_9"> <img data-normal="images/bike.jpg" data-retina="images/bike@2x.jpg" alt="Bike"> </div>
重新加载浏览器预览并调整视口大小:您现在应该发现所有图像都是响应式的,同时保留了它们的延迟加载功能,没有重排。
6.添加srcset
Layzr 也支持srcset 属性。在支持 srcset 的浏览器中,它将优先于data-normal和 使用data-retina。
然而,与其使用直接srcset属性,不如在前面加上data-我们目前使用的其他属性。
将第一张图片的代码更新为:
<img data-normal="images/vulture.jpg" data-retina="images/vulture@2x.jpg" alt="Vulture" data-srcset="images/vulture_sml.jpg 320w, images/vulture_med.jpg 640w, images/vulture.jpg 960w">
要查看此工作,请转到浏览器预览,将视口缩小到 320 像素以下,重新加载并观看网络面板。您应该首先看到图像加载的最小版本。然后增加视口的大小,您应该会看到中型和大型版本的加载。
源文件中提供的图像文件夹包括每个图像的小、中和大版本。data-srcset更新您的代码以在您的属性中使用它们,如下所示:
<div class="ratio_3_2"> <img data-normal="images/vulture.jpg" data-retina="images/vulture@2x.jpg" alt="Vulture" data-srcset="images/vulture_sml.jpg 320w, images/vulture_med.jpg 640w, images/vulture.jpg 960w"> </div> <div class="ratio_3_2"> <img data-normal="images/beach.jpg" data-retina="images/beach@2x.jpg" alt="Beach" data-srcset="images/beach_sml.jpg 320w, images/beach_med.jpg 640w, images/beach.jpg 960w"> </div> <div class="ratio_3_2"> <img data-normal="images/bear.jpg" data-retina="images/bear@2x.jpg" alt="Bear" data-srcset="images/bear_sml.jpg 320w, images/bear_med.jpg 640w, images/bear.jpg 960w"> </div> <div class="ratio_16_9"> <img data-normal="images/sky.jpg" data-retina="images/sky@2x.jpg" alt="Sky" data-srcset="images/sky_sml.jpg 320w, images/sky_med.jpg 640w, images/sky.jpg 960w"> </div> <div class="ratio_16_9"> <img data-normal="images/bike.jpg" data-retina="images/bike@2x.jpg" alt="Bike" data-srcset="images/bike_sml.jpg 320w, images/bike_med.jpg 640w, images/bike.jpg 960w"> </div>
添加加载动画
我们几乎完成了,但要创建最后一层抛光,我们将添加一个加载动画。这将有助于向访问者传达布局的哪些部分充当图像占位符,以及这些图像正在加载过程中。
我们将使用纯 CSS 加载器,这是 Alan Shortis 对这支伟大笔的略微修改版本: https ://codepen.io/alanshortis/pen/eJLVXr
为了避免需要任何额外的标记,我们将加载动画包含在:after附加到每个纵横比包装器的伪元素中。将以下内容添加到您的 CSS 中:
div[class^="ratio_"]:after { content: ''; display: block; width: 3rem; height: 3rem; border-radius: 50%; border: .5rem double #444; border-left: .5rem double white; position: absolute; top: calc(50% - 2rem); left: calc(50% - 2rem); animation: spin 0.75s infinite linear; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
上面的代码创建了一个小的圆形加载器图标,将其居中,并使其每 0.75 秒旋转 360 度。
我们还将为纵横比包装器添加深灰色背景颜色,以便轻松将它们与布局的其余部分区分开来。background-color: #333; 如下添加此行:
div[class^="ratio_"]{ position: relative; width: 100%; background-color: #333; }
最后,我们只需要确保我们的加载器不会将自己定位在图像的顶部。为此,我们将向z-index: 1;我们的图像添加该行,将它们移动到加载器顶部的一个层:
div[class^="ratio_"] > img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; }
现在刷新您的页面,您应该会看到正在运行的加载动画。
你的最终代码
完成上述所有操作后,您的代码现在应该如下所示:
<!doctype html> <html> <head> <meta charset="utf-8"> <title>Layzr.js Lazy Loading</title> <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1"> <style> body { margin: 0; } img { display: block; margin: 0 auto; } div[class^="ratio_"]{ position: relative; width: 100%; background-color: #333; } .ratio_3_2 { /*padding-top: calc( 100% * (2 / 3) );*/ padding-top: 66.666667%; } .ratio_16_9 { /*padding-top: calc( 100% * (9 / 16) );*/ padding-top: 56.25%; } div[class^="ratio_"] > img { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; } div[class^="ratio_"]:after { content: ''; display: block; width: 3rem; height: 3rem; border-radius: 50%; border: .5rem double #444; border-left: .5rem double white; position: absolute; top: calc(50% - 2rem); left: calc(50% - 2rem); animation: spin 0.75s infinite linear; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } </style> </head> <body> <h1>Welcome to the lazy loaded web</h1> <div> <img data-normal="images/vulture.jpg" data-retina="images/vulture@2x.jpg" alt="Vulture" data-srcset="images/vulture_sml.jpg 320w, images/vulture_med.jpg 640w, images/vulture.jpg 960w"> </div> <div> <img data-normal="images/beach.jpg" data-retina="images/beach@2x.jpg" alt="Beach" data-srcset="images/beach_sml.jpg 320w, images/beach_med.jpg 640w, images/beach.jpg 960w"> </div> <div> <img data-normal="images/bear.jpg" data-retina="images/bear@2x.jpg" alt="Bear" data-srcset="images/bear_sml.jpg 320w, images/bear_med.jpg 640w, images/bear.jpg 960w"> </div> <div> <img data-normal="images/sky.jpg" data-retina="images/sky@2x.jpg" alt="Sky" data-srcset="images/sky_sml.jpg 320w, images/sky_med.jpg 640w, images/sky.jpg 960w"> </div> <div> <img data-normal="images/bike.jpg" data-retina="images/bike@2x.jpg" alt="Bike" data-srcset="images/bike_sml.jpg 320w, images/bike_med.jpg 640w, images/bike.jpg 960w"> </div> <script src="https://cdnjs.cloudflare.com/ajax/libs/layzr.js/2.0.2/layzr.min.js"></script> <script> const instance = Layzr() document.addEventListener('DOMContentLoaded', function(event){ instance.update().check().handlers(true) }) </script> </body> </html>
最后
您现在已经完全手动实现了延迟加载,并尽可能接近 AMP 的功能。
AMP 会自动执行一些操作,例如处理响应式图像的纵横比保留,但另一方面,您自己做的事情允许增加控制,例如指定您自己的加载阈值。
- 创建html外壳
- 加载Layzr
- 实例化 Layzr
- 确保图像具有高度
- 添加纵横比包装器
- 添加标准纵横比样式
- 给出纵横比包装高度
- 用图像填充纵横比包装器
- 添加额外的纵横比