next.js amp
谷歌的“加速移动页面”(AMP)项目最近帮助影响了网站变得更快。 凭借出色的技术和强大的内容分发网络,Google直接提高了AMP增强的网站的速度。 但是,AMP还通过鼓励我们研究AMP包含的优化和最佳实践来间接工作。 即使您不打算使您的网站符合AMP,将AMP理解为优化非AMP网站的待办事项列表也是很有用的。
此列表中的一项优化是一种称为“延迟加载”的技术,我们在最近的有关使用AMP的<amp-img> 自定义元素的文章中看到了这一点。 使用此技术,当访问者第一次到达页面时,仅加载视口中或视口附近的图像。 当访客向下滚动时,将触发其余的加载。
延迟加载使访问者可以更快地开始与内容互动,而增强的加载速度可以提高搜索引擎排名。 页面上的图像越多,您获得的速度改进就越大。
在本教程中,我们将研究如何使用名为Layzr.js的脚本在非AMP网站上部署延迟加载。 我们将尽可能地复制AMP的<amp-img>
元素的功能,但是我们还将使用Layzr特有的某些功能。
让我们开始!
1.基本设置
作为文章“ AMP项目:它将使您的站点更快? ”的一部分,我创建了一个包含五个图像的基本布局。 为了使您能够在使用AMP和自己部署延迟加载之间进行比较,我们将重新创建相同的五个图像布局。 在本教程的后面,我将向您展示如何运行各种负载速度测试。
在本教程所附的源文件中,您将找到布局的AMP版本,以及在此处制作的完整版本。 两者都可以帮助您确定哪种方法最适合您。
在逐步进行所有操作时,建议您使用Chrome开发工具( F12 )在“ 网络”标签处于打开状态,选中“ 禁用缓存”并将限制设置为“ 常规3G”的情况下测试您的工作。 这模拟了一个平均的移动连接,向您实时显示了每个图像加载的图表,并有助于您清晰地了解延迟加载的工作方式。
刷新测试页面时,请按住重新加载按钮,这将使下拉菜单显示为不同的选项。 选择“ 清空缓存”和“强制重新加载”以完全模拟首次访问您网站的访问者。
创建HTML Shell
让我们从基础知识入手。 首先,创建一个文件夹来容纳您的项目,并在其中创建一个名为index.html的文件。
打开它进行编辑,并添加以下代码:
<!doctype html>
<html lang="en">
<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 Shell,并包含一些CSS以确保页面的body
和图像之间没有任何意外的间隙。
我们还包括margin: 0 auto;
因此我们稍后添加的图像将居中。
加载Layzr
layzr.js脚本具有两个方便的CDN源,您可以从中加载它们-我们将使用一个来自Cloudfare的源。
在结束</body>
标记之前,将此代码添加到您的html中。
<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 lang="en">
<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">
现在,使用data-normal
属性(包括height
和width
)为要加载的每个图像在脚本标签上方添加img
元素。
<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%,例如1200px。 在这种情况下,视口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">
更新HTML中的所有img
元素以包括视网膜图像,如下所示:
<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
将成为我们的占位符。 我们将使用CSS调整div
本身的大小,然后将其中的图像设置为水平和垂直填充。
我们将给div
一个类名,该类名代表它将包含的图像的长宽比。 在我们的示例中,第一张图像的宽度为960像素,高度为640像素,因此让我们找出产生它的宽高比。
640(我们的高度)是960(我们的宽度)的三分之二,这意味着每2个高度单位,图像就有3个宽度单位。 像众所周知的16:9
一样,长宽比通常表示为width:height
。 我们的第一个示例图像的比例为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
的宽度如何,此填充都将使高度保持在该值的66.666667%(三分之二),因此保持我们的3:2长宽比。
要确定要在此处放置的百分比,请找出以纵横比表示的宽高比是多少。 您可以通过计算来做到这一点:
100% * (height / width)
对于我们的3:2比例: 100% * (2 / 3) = 66.666667%
您可以预先为所需的宽高比计算正确的百分比,或者,如果愿意,可以使用CSS calc()
函数,如上例所示:
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
。
但是,与其使用straight 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">
要查看此工作,请转到浏览器预览,将视口缩小到320px以下,重新加载并观看网络面板。 您应该首先看到最小版本的图像。 然后增加视口的大小,随行随即会看到加载中型和大型版本。
源文件中提供的images文件夹包括每个图像的小,中和大版本。 更新代码以在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
psuedo-element中包含加载动画: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 lang="en">
<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 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>
<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会自动执行几项操作,例如在响应图像上处理宽高比保留,但是在另一面,您自己可以执行其他操作,例如指定自己的加载阈值。
希望通过此过程可以帮助您确定首选的方法。
next.js amp