第4节 关键CSS
4.1 原理
关键CSS将样式分为两类:首屏样式和页面其余部分的样式
只将首屏样式存储到
<style>
标签中,并将其内联到HTML,剩下的样式将从外部文件加载。使用标签加载CSS,而是使用preload提示
1
<link rel="preload" href="..." as="style" onload="this.rel=stylesheet'" >
这样可以在不阻塞渲染的情况下加载CSS。CSS完成下载时,标签上的onload事件处理程序将被触发。下载完成后,rel属性的值就会从preload转换为stylesheet。这将标签从资源提示更改为普通CSS引入,后者将CSS应用于首屏以外的内容。JavaScript polyfill作为兜底,以防浏览器不支持preload提示。就是这么简单!
4.2 实现
- 480、667、768、800、900、1024和1280像素的位置绘制参考线。这些是流行设备的常见垂直分辨率,并且大多数设备的分辨率包含在两者之间的任何位置。制作好这些参考线后,你需要调整浏览器窗口的大小,以查看内容在每个临界点上的位置。
- 网站mydevice.io列出了各种设备的分辨率
第5节 响应式图像
5.1 通过媒体查询适配高DPI显示器
-webkit-min-device-pixel-ratio媒体查询会检查像素密度的简单比率,其中比率1相当于96DPI。这种情况下,在下载更高分辨率的图像之前,你要确保显示器的像素密度至少为192DPI。而min-resolution媒体查询则采用更直接的值192dpi。
1
@media screen (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) and (min-width: 30em){ /* High DPI 480px/16px */ #masthead{ }}
5.2 使用SVG
JPEG和全彩PNG文件更适合照片,而SVG最适合用于logo、线条艺术和图案等内容
使用的两种方法
标签使用
1
<img src="img/logo.svg" />
标签中使用SVG文件时,几乎没有理由在
内嵌使用
1
<svg xmlns="...." id="logo"> ....</svg>
这种方式有优势也有劣势。优点是,内嵌SVG可以通过删除HTTP请求来帮助减少页面加载时间,前提是你的站点没有托管在HTTP/2服务器上。缺点是,这也使得资源在页面间的可缓存性降低。
5.3 在HTML中传输图像
5.3.1 max-width规则
对所有img元素设置的全局max-width规则
1
img{ max-width:100%}
这条简洁的规则有很多好处,其一是使任何元素都渲染为其自然宽度(除非超过容器)。超过容器宽度时,这条规则会将图像宽度限制为容器的宽度
5.3.2 srcset
显示响应式图像的方法之一,是在标签中使用名为srcset的HTML5特性。标签的这个可选属性不是替换src属性,而是对其进行补充。
使用srcset具体说明图像
1
<img src="image-small.jpg" srcset="image-medium.jpg 640w,image-large.jpg 1280w"/>
srcset属性的格式是图像URL和图像宽度,由空格分隔。图像名称采用标签的src属性中常用的格式,宽度使用后缀w表示
srcset的优点是,它不需要媒体查询就可以工作。浏览器获取给定的信息,并根据视口的当前状态选择最适合的图像。
5.3.3 使用size控制粒度
它接受一组媒体查询和宽度,媒体查询起作用时,它后面的宽度设置图像应显示的宽度。这些媒体查询和图像大小可以通过逗号分隔成多对使用
1
<img src="image-small.jpg" srcset="image-medium.jpg 640w,image-large.jpg 1280w" sizes="(min-width:704px) 50vw, 100vw" />
size属性的内容有两个作用。在704像素和更宽的屏幕上,指示图像占据视口宽度的50%。要告诉浏览器这一点,可以使用视口宽度(vw)单位,即视口当前宽度的百分比。之后的下一个逗号分隔规则(不带媒体查询)是图像的默认宽度。
5.3.4 使用picture元素
为不同的屏幕提供不同的裁剪和焦点的实践
可以在
中添加一些 1
<picture> <source media="(min-width:704px)" srcset="img/amp-medium.jpg 384w" sizes="33.3vw"/> <source srcset="img/amp-cropped-small.jpg 320w" sizes="75vw"> //浏览器不支持picture的回退操作 <img src="img/amp-small.jpg"> </picture>
可以轻松使用
元素应对高DPI显示器。这需要对 1
<picture> <source media="(min-width:704px)" srcset="img/amp-medium.jpg 384w, img/amp-large.jpg 512w, " sizes="33.3vw"/> <source srcset="img/amp-cropped-small.jpg 1x, img/amp-cropped-medium.jpg 2x, " sizes="75vw"> //浏览器不支持picture的回退操作 <img src="img/amp-small.jpg"> </picture>
为了告知浏览器哪种图像应用于哪种类型的显示器,在srcset属性中使用x值代替宽度值。可以把它想象成一个简单的乘数,1x标记适合标准DPI屏幕的图像,2x或更高的倍数表示适合于更高DPI屏幕的图像。如果愿意,甚至可以使用3x或更高的倍数,因为5K显示器已经逐渐普及了。
5.3.5 使用Picturefill提供polyfill支持
尽管srcset和
都很有用,但它们的浏览器支持并不是通用的。值得庆幸的是,可以通过一个名为Picturefill的11KB小脚本,在不支持这些标签浏览器中使用它们。 从https://scottjehl.github.io/picturefill/下载Picturefill并将其放入项目
第一个
<script>
块用于无法识别元素的浏览器,并防止在Picturefill完成加载之前,浏览器在HTML中解析它们时出现问题。第二个块加载Picturefill库,但使用async属性,因而不会阻塞页面渲染 1
<script>document.createElement("picture")</script><script src="js/picturefill.min.js" async></script>
5.3.6 使用Modernizr选择性加载Picturefill
使用Modernizr来避免在现代浏览器中加载11KB的Picturefill库,方法是首先检查浏览器是否需要它。
1
<script src="js/modernizr.custom.min.js"></script><script> if(Modernizr.srcset === false || Modernizr.picture === false){ var picturefill = document.createElement("script") picturefill.src = "js/picturefill" document.body.appendChild(picturefill) }</script>
第6节 图像处理
6.1 雪碧图
图像雪碧图会组合图像以减少HTTP请求,这是一种连接。尽管你应该在HTTP/1上使用图像雪碧图来提高页面加载速度,但是应当避免在HTTP/2上使用它们。
雪碧图就是把以往在整个网站中使用的单独的图像文件集合起来,组成一个图像文件。这些图像通常是图标等全局元素。
使用
安装包
1
npm install -g svg-sprite
使用svg-sprite命令
使用生成的雪碧图,将生成的图片替换之前使用的即可
创建包含特定于内容的图像雪碧图,会迫使用户下载可能不使用它的页面的内容。每个场景都是独特的,所以可以列出一个图像清单,然后创造最适合你的网站的雪碧图。
Grumpicon网站是一个基于Web的工具,它接受SVG文件,并生成带有回退选项的PNG版本的雪碧图
6.2缩小图像
6.2.1 优化JPEG图像
使用imagemin
1
npm install imagemin imagemin-jpeg-recompress
创建一个存放生成文件的文件夹
1
mkdir optimg
编写优化Node程序
1
//reduce.jsvar imagemin = require('imagemin'), jpegRecompress = require('imagemin-jpeg-recompress')imagemin(["img/*.jpg"],"optimg",{ plugins:[ jpegRecompress({ accurate:true, //设置精确度优先于速度 max:70 //输出图像最大JPEG质量 }) ]}) //输出到optimg文件夹
执行
1
node reduce.js
最后将标签引用更改为指向optimg文件夹中的文件。imagemin-jpeg-recompress并不是唯一的JPEG优化库。
6.2.2 优化PNG图像
使用以下命令下载imagemin-optipng插件:
1
npm install imagemin-optipng
编写Node程序,其余同上
1
//reduce.jsvar imagemin = require('imagemin'), jpegRecompress = require('imagemin-optipng')imagemin(["img/*.jpg"],"optimg",{ plugins:[ optipng() ]}) //输出到optimg文件夹
6.2.3 优化SVG
安装包
1
npm install -g svgo
使用命令转换svg
1
svg -o b.svg a.svg
这个命令的格式很简单。它以-o参数开始,该参数是svgo写入优化后的输出的文件名称。之后是未优化的SVG文件的名称。
svgo程序功能强大,有很多选项。也许我们应该深入看看你是否可以进一步优化这个图像。键入svgo -h可以查看其他选项。值得留意的是-p参数,可以使用它来控制浮点数的精度。尝试将此值设置为1。不过,不要太仓促地宣布成功。应该观察输出,看看是否引入了任何异常。
6.3 使用WebP编码图像
- 栅图像仅有的可用选项就是JPG、GIF和PNG格式。几乎再没有新的格式出现,直到几年前Google引入了WebP。WebP同时支持有损和无损格式编码。
- Google的WebP格式是一个很好的例子。WebP很强大,它根据图像内容提供比等效格式更小的文件
6.3.1 使用imagemin编码有损WebP图像
需要安装imagemin和imagemin-webp插件:
1
npm install imagemin imagemin-webp
使用imagemin将JPEG图像编码为有损WebP图像,其余同上文
1
//reduce.jsvar imagemin = require('imagemin'), jpegRecompress = require('imagemin-webp')imagemin(["img/*.jpg"],"optimg",{ plugins:[ webp({ quality:40 //将Webp编码器的质量设置为40,最大为100 }) ]}) //输出到optimg文件夹
6.3.2 使用imagemin编码无损WebP图像
其余同上文
1
//reduce.jsvar imagemin = require('imagemin'), jpegRecompress = require('imagemin-webp')imagemin(["img/*.jpg"],"optimg",{ plugins:[ webp({ lossless:true }) ]}) //输出到optimg文件夹
6.3.3 支持度不高
尽管WebP是一种很好的图像格式,你也可以从现在开始使用它,但它的支持并不像成熟的图像格式那么广泛。如果你的用户依赖于Firefox或Safari等浏览器
你需要指定其他浏览器可以处理的回退。要在
元素中创建一系列回退,可以在 1
<picture> <source srcset="img/amp-small.webp" type="image/webp"/> <img src="img/amp-small.jpg"/></picture>
6.4 懒加载图像
为懒加载程序配置标记是任务中最省时的部分,但它至关重要。你需要一个阻止浏览器默认加载图像的模式。将srcset和src属性移动到data属性,这样图像就不会加载;为需要懒加载脚本处理的
<img>
元素添加一个类。<source>
和<img>
元素上的所有srcset和src属性更改为data-srcset和data-src属性。将图像URL存储在这些占位符属性中可以跟踪图像来源,同时防止它们加载,直到你希望加载为止。然后,在<img>
标签上创建一个新的src属性,指向具有灰色背景的16像素×9像素占位符PNG。这通过引入占用同等空间的占位符,使布局的变化保持最小。最后一步是将lazy类添加到<img>
标签。这就是在需要加载图像时懒加载程序脚本的目标元素。懒加载脚本lazyload.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116(function (window, document) {
;
var lazyLoader = {
lazyClass: "lazy",
//html中,懒加载图像使用的类名
images: null,
//图像元素集合
processing: false,
//处理状态,用于限制执行
throttle: 200,
//节流时间
buffer: 50,
//视口缓冲大小,用于加载视口边缘附近图像
init: function () {
//开始懒加载
lazyLoader.images = [].slice.call(document.getElementsByClassName(lazyLoader.lazyClass));
//通过lazyClass属性定义的类名,获取所有元素
lazyLoader.scanImages();
//初始化时运行scanImages
document.addEventListener("scroll", lazyLoader.scanImages);
//滚动时运行scanImages
document.addEventListener("touchmove", lazyLoader.scanImages);
////触摸屏幕时运行scanImages
},
destroy: function () {
//页面删除懒加载行为
document.removeEventListener("scroll", lazyLoader.scanImages);
//移除行为
document.removeEventListener("touchmove", lazyLoader.scanImages);
//移除行为
},
scanImages: function () {
if (document.getElementsByClassName(lazyLoader.lazyClass).length === 0) {
//检查是否还有文件需要懒加载
lazyLoader.destroy();
//所有图像都已加载则析构加载程序
return;
}
if (lazyLoader.processing === false) {
//检查文档是否正在被检查图像
lazyLoader.processing = true;
//阻塞后续代码执行
setTimeout(function () {
//将代码块处理延迟指定时间
for (var i in lazyLoader.images) {
//循环集合中所有图像
if (lazyLoader.images[i].className.indexOf("lazy") !== -1) {
//检查图像是否在视口中
if (lazyLoader.inViewport(lazyLoader.images[i])) {
//检查元素是否包含lazy类
lazyLoader.loadImage(lazyLoader.images[i]);
//将当前元素传递给loadImage方法
}
}
}
lazyLoader.processing = false;
//关闭标志位
}, lazyLoader.throttle);
//通过throttle属性指定超时时间
}
},
inViewport: function (img) {
//核心懒加载方法
//寻找视口的位置和高度以及缓冲阈值
var top = ((document.body.scrollTop || document.documentElement.scrollTop) + window.innerHeight) + lazyLoader.buffer;
return img.offsetTop <= top;
//检查给定的图像是否在视口中
}, loadImage: function (img) {
if (img.parentNode.tagName === "PICTURE") {
//检查图像父节点是否为picture
var sourceEl = img.parentNode.getElementsByTagName("source");
//获取附近source元素
for (var i = 0; i < sourceEl.length; i++) {
//遍历source元素
var sourceSrcset = sourceEl[i].getAttribute("data-srcset");
//获取data-srcset属性用于加载图像
if (sourceSrcset !== null) {
//检查source元素是否存在srcset属性
sourceEl[i].setAttribute("srcset", sourceSrcset);
//设置srcset s
ourceEl[i].removeAttribute("data-srcset");
//删除data-srcset
}
}
}
//从img元素中获取data-src和data-srcset
var imgSrc = img.getAttribute("data-src"),
imgSrcset = img.getAttribute("data-srcset");
//检查img元素上是否存在data-src
if (imgSrc !== null) {
//将img的src设置为data-src属性的值
img.setAttribute("src", imgSrc);
img.removeAttribute("data-src");
}
//同上
if (imgSrcset !== null) {
img.setAttribute("srcset", imgSrcset);
img.removeAttribute("data-srcset");
}
//移除img元素的懒加载类名
lazyLoader.removeClass(img, lazyLoader.lazyClass);
},
//移除类名中包含lazy的类名
removeClass: function (img, className) {
var classArr = img.className.split(" ");
for (var i = 0; i < classArr.length; i++) {
if (classArr[i] === className) {
classArr.splice(i, 1);
}
}
img.className = classArr.toString().replace(",", " ");
}
};
//开始运行脚本
document.onreadystatechange = lazyLoader.init;
})(window, document);将脚本放入html中使用
有些用户关闭了JavaScript或者无法使用JavaScript。方法是添加
<noscript>
标签,在src和srcset属性中显式设置图像源。1
<noscript> <picture> <source ...> </picture></noscript>
第7节 影响脚本加载行为
通过修改
<script>
标签的async属性实现的。async(asynchronous的缩写)告诉浏览器加载脚本后立即执行该脚本且不会在下载时阻塞渲染,而不是按顺序加载每个脚本并等待它们依次执行。使用async
1
<script src="js/jquery.js" async></script>
使用注意,如果没有任何脚本具有依赖项,则可以自由使用async。但是当脚本具有依赖性时,事情就会变得棘手。解决此问题的一种方法是将依赖脚本组合起来,以便将这些依赖打包为单个资源。在本例中,可以按顺序组合jquery.min.js和behaviors.js。在命令行中运行此命令,将两个脚本合并为scripts.js:
第8节 使用 Service Worker 提升性能
- 有时我们会离线,例如,在没有Wi-Fi的飞机上,或汽车、火车通过隧道的时候。这就是现实生活。这种情况发生时,我们已经习惯于无法浏览网站。但不一定非要这样,这就是ServiceWorker出现的原因。
第9节 微调资源传输的方法
9.1 服务器压缩资源
为客户网站压缩资源时,通过npm下载的compression模块。这个模块使用gzip算法——最常用的压缩算法。可以通过传递选项来修改此模块应用的压缩级别。可以通过level选项指定0~9的数字修改压缩级别,其中0代表不压缩,9代表最大值
1
app.use(compression({ level:7}))
将压缩级别设置为9并非总是最佳策略。level设置得越高,CPU压缩响应所需的时间就越多。压缩内容所花费的额外CPU时间可能会使问题复杂化,并使总体性能变差。我能给出的最好建议是:在有效载荷大小和压缩时间之间取得平衡。大多数情况下,默认压缩级别6是最适合的,但是你自己的测试才是最权威的信息源。
针对压缩哪些类型的文件给出了两个建议:总是压缩文本文件类型(因为它们的压缩效果很好),以及避免压缩内部已经压缩的文件。你应该避免压缩大多数图像类型(SVG除外,SVG属于XML文件)和字体文件类型,如WOFF和WOFF2。在Node Web服务器上使用的compression模块不会尝试压缩所有内容。如果要压缩所有资源,必须通过filter选项传递函数来告诉它
1
app.use(compression({ filter:function(request,response){ return true; }}))
在我的测试中,我比较了所有压缩级别下JPEG、PNG和SVG图像的压缩比
如你所见,PNG和JPEG根本不能压缩。SVG压缩得很好,因为它们是由可压缩的文本数据组成的。
gzip一直是首选的压缩方法,而且这种情况似乎不会很快改变。但一个有前途的新竞争者已经登场——Brotli。
尽管Brotli在某些方面的性能与gzip相当,但它显示出了良好的前景,并在不断地发展。考虑到这一点,Brotli值得你考虑。但是查看Brotli性能之前,先看看如何在浏览器中检查Brotli支持情况。如果有Chrome 50或更高版本,请打开开发者工具,转到任一启用HTTPS的网站上的Network选项卡,查看任一资源的Accept-Encoding请求头的值。
1
accept-encoding:gzip,deflate,sdch,br
如果浏览器支持Brotli,它会在Accept-Encoding请求头中将br令牌包含在接受的编码列表中。当支持它的服务器看到这个令牌时,就会用Brotli压缩的内容进行回复,否则它应该回退到下一个支持的编码方案。
9.2 缓存资源
9.2.1缓存Cache-Control
Cache-Control头部。这个头部几乎在每一个浏览器中都指示缓存行为。通过其max-age指令,该指令指定缓存资源的生命周期(以秒为单位)
1
Cache-Control:max-age = 3600
当用户再次访问时,请求的资源对于max-age指令中指定的时间量(3600秒,或者更直观地说,一小时)之内的缓存是有效的。
9.2.2 服务器检查资源是否更改
- 流行的方法是使用实体标记,简称ETag。ETag是根据文件内容生成的校验和。浏览器将这个值发送到服务器,服务器将通过验证该值来查看资源是否已更改。另一个方法是检查文件在服务器上最后修改的时间,并根据上次修改时间提供资源的副本。可以使用Cache-Control头部修改此行为。
- max-age指令对于大多数网站来说都是适合的,但有时候你需要限制缓存行为或者完全取消它。使用no-cache、no-store、stale-while-revalidate控制资源重新验证
9.2.3 CDN
可以在网站前使用CDN。CDN是一种代理服务,它位于你的网站前面,并优化内容向用户的传输。CDN将你的资源托管在它们的服务器网络上,因此CDN可以有效缓存你的内容。可以将两个Cache-Control指令(public和private)与max-age结合使用,它们能够帮助你控制CDN缓存内容的方式。
1
Cache-Control: public,max-age=86400
9.3 制定最佳缓存策略
对资源进行分类时,最好的标准是资源更改的频率。例如,HTML文档可能经常更改,而CSS、JavaScript和图像等资源更改的可能性要小一些。
展示了这些资源类型的细分,以及我为它们选择的缓存策略。
❏ HTML文件或者输出HTML的服务器端语言(例如PHP或ASP.NET)可以受益于保守的缓存策略。你永远不会希望浏览器假定页面应该只从浏览器缓存中读取,而不去重新验证其新鲜度。
- no-cache可以确保总是重新验证资源,如果资源已更改,则下载新的副本。如果文件的内容没有更改,则重新验证资源确实会减少服务器上的负载,但是no-cache不会激进地缓存HTML,以避免内容过时。
- max-age为一小时,可确保在max-age到期后,无论发生什么情况,都获取资源的新副本。
- 使用private指令告诉位于Web源服务器前面的任何CDN:该资源根本不应缓存在其服务器上,而应仅在用户和Web源服务器之间缓存。
CSS和JavaScript是重要的资源,但不需要如此积极地缓存。因此,你可以使用30天的max-age。因为你会从为你分发内容的CDN中受益,所以应该使用public指令允许CDN缓存资源。
图像和其他媒体文件(如字体)几乎不会改变,而且这些往往是你要提供的最大的资源。因此,把max-age设置为较长的时间(比如一年)比较合适。
- 与CSS和JavaScript文件一样,你会希望CDN能够缓存这些资源。因此此处自然也要使用public指令。
- 因为这些资源不会经常变化,所以你希望有一个宽限期,在这个宽限期内可以接受某种程度的过时资源,因此,一天的stale-while-revalidate周期适合用来给浏览器异步验证资源的新鲜度。
9.4 实现缓存策略
自定义
http.js
测试:不要重新加载页面,因为这将导致浏览器与服务器联系以重新验证资源。正确做法是导航到页面。要在已经打开的页面上执行此操作,请点击地址栏并按回车键。
9.5 使缓存资源失效
使CSS和JavaScript资源失效。假设使用php时,强制CSS更新,其他语言也有替代方案
使图像和其他媒体文件失效。明智的选择可能是指向一个新的图像文件。
9.6 使用CDN
可以将这个
<script>
标签的src属性更改为指向由MaxCDN免费提供的CDN托管版本的库:1
<script src="https://code.jquery.com/jquery-2.2.3.min.js"></script>
以下是为你提供各种资源的简短的CDN列表。
- cdnjs是一个托管了几乎所有流行的(或不那么流行的)库的CDN。它提供了干净的界面,使你能够搜索任何可以想到的常用CSS或JavaScript资源,例如广泛使用的MVC/MVVM框架、jQuery插件,或项目所依赖的任何其他东西。
- jsDelivr是另一个类似于cdnjs的CDN。如果cdnjs没有提供你想要的内容,请尝试在这里搜索
- Google CDN涵盖的库比cdnjs或jsDeliv少得多,但它确实提供了流行的库(如Angular和其他库)。在我的测试中,这是最快的CDN。
建议
- 另一个建议是:如果你使用的库(如Modernizr或Bootstrap)可以配置为提供该库功能的特定部分,请配置你自己的构建,而不要指向CDN上的整个库。有时候,配置一个较小的构建并将其托管在自己的服务器上,比从CDN引用完整的构建要快。理清你的需求,并比较哪种方法更好。
- 如果你使用的库(如Modernizr或Bootstrap)可以配置为提供该库功能的特定部分,请配置你自己的构建,而不要指向CDN上的整个库。有时候,配置一个较小的构建并将其托管在自己的服务器上,比从CDN引用完整的构建要快。理清你的需求,并比较哪种方法更好。
CDN失效指定回退版本
9.7 使用资源提示
资源提示可用于加快网页加载速度、微调特定页面资源的传输,以及预渲染用户尚未访问的页面。
在功能强大的浏览器中,prefetch告诉浏览器下载特定资源,并将其存储到浏览器缓存中。这个资源提示可以像请求那样,用于预取位于同一页面上的资源;或者你也可以对用户下一步可能访问的页进行猜测,并请求那个页面的资源。使用第二种方法时要特别小心,因为它可能会迫使用户下载不必要的资源。
preload资源提示与prefetch非常相似,只是它保证将下载指定的资源。script、style、font和image的值可以分别用于JavaScript、CSS、字体和图像。使用as属性描述请求的内容类型。此属性是完全可选的,但省略它是不利的,因为如果没有as,浏览器将下载该资源两次。
使用
1
<link rel="preload" href="https://code.jquery.com/jquery-2.2.3.min.js" as="script">
必须在preload和prefetch之间进行选择,请选择prefetch。如果浏览器支持没有那么重要,并且希望无论怎样请求都能够抢先加载内容,请选择preload。
第10节 HTTP/2必要性
- HTTP/2 :解决HTTP1协议的三大问题
- 队首阻塞、未压缩头部和缺少HTTPS的授权。