Web性能优化
这个标题起的很大啊,其实就是看的《Web性能实战》这本书,里面有讲到一些优化前端的方法,在这里记录一下,水一篇文章。只是简单记录一下哪些优化的点,并不是详细介绍,书是作者15年写的,甚至好像还没赶上ES6的语法,不知道有些东西有没有可以进步的空间,但性能优化这方面应该还好,毕竟就是那几样。有些工具也比较老了,毕竟是15年的,后起之秀应该有更好的,而且有些打包工具帮我们做好了,比如webpack,vite,具体怎么优化可以根据时代变化而不同,但是优化哪些东西还是亘古不变的。
压缩
很简单,这点甚至不想要小标题,就是压缩。
-
缩小——在文本资源中取出所有空白或者非必要字符的过程(作者使用html-minify,分别去压缩css,js和html,有点类似xxx.min.js,但还没到那种混淆字符的地步)
-
服务器压缩——客户端请求头附带Accept-Encoding告诉服务器自己的压缩格式,服务器如果能够相应,就会返回Content-Encoding的头部信息告知压缩方法和压缩内容。常见的有gzip和Brotli算法(b站我之前看了,用的就是br压缩算法)。然后nginx和apache也可以去开启压缩算法,具体可以自查。
http请求头:Accept-Encoding: gzip, deflate, sdch, br http返回头:Content-Encoding: br -
压缩图像——这个简单,就是压缩图像,我博客也用,这块提升非常显著,因为图像太大了,实属影响体验
优化CSS
保持一个原则,DRY(Don’t repeat yourself,不用重复你自己)
-
简写CSS,不用冗长的属性和属性值(虽然提升很小,但是大型网页,积少成多,margin、padding、border这些都考虑能用简写就简写
p{ font-family: "Arial", "Helvetica", sans-serif; font-size: 0.75rem; font-style: italic; } 建议写成一条 p{ font: italic 0.75rem "Arial", "Helvetica", sans-serif; } -
使用CSS浅选择器,不要写太具体的
header div.phoneNumber h3.numberHeader 重写成 .numberHeader -
less和sass虽然编写方便,但是多层嵌套,就会使得编译后的选择器变得非常具体,不仅会增大体积,而且会延长渲染时间
-
合并同类项,如果有几个css的元素大部分属性都是相同的,那么就用合并的写法写
-
分割CSS文件,按照特定页面模板拆分CSS样式,这样子用户不会跳到的子页面就不会下载css。
-
避免使用
@import,因为@import是串行请求,会阻塞,而link标签是并行请求 -
在
head标签放置CSS的引用,可以解决无样式内容的闪烁问题,以及加载时提高页面的渲染性能(这个算是常识吧,大家都这么干,没见过谁把css引入放到body中的) -
标签选择器有讲究,速度是标签>后代>类>直接子元素>过度定义>>兄弟>伪类>>属性
-
尽可能使用flexbox,对比盒子模型快的一批
-
简单动画使用CSS过度而不是JavaScript脚本来操作动画,能够节约资源(表现不一定会更好)
-
link虽然很棒,但是也是会阻碍渲染,可以使用关键CSS。关键CSS简单理解就是,你刚打开网页视口能够看到的那部分界面的CSS,把这些CSS内联到HTML中,剩下的样式从外部加载。首屏以外的内容可以用preload加载,如下所示,这种方式不会阻碍渲染,当CSS完成加载后,onload事件会触发,并修改
<link>标签的rel使得值渲染。<link rel="preload" href="css/style.min.css" as="style" onload="this.rel=stylesheet"> -
关键CSS的分割,麻烦之处需要支持移动端、平板端、PC端三个地方都需要去优化,因为窗口大小不一样,首屏显示的内容也不一样,CSS样式需要调整
图片
图片占大头,所以需要好好优化
- 在响应式布局中,传递正确的图像源给合适的设备可以对加载和处理时间产生积极影响。按照不同的设备大小传输不同的图片大小,按照不同的DPI也可以传送不同大小的图片
- 使用srcset属性和
<picture>元素在html中传递响应式图像。(因为一般的都是在css中用@media来处理设备大小的,html也可以直接处理了) - 某些图片使用SVG更加,比如LOGO这些。
- 使用雪碧图,雪碧图就是把那些单独的图像合并在一起,形成了一个图像文件,比如把多个svg图片合并成一个css样式文件,把多个png图片合并成一个更大的png图片,然后通过css的大小限定和位移来展示图片,比如轮播图就可以这么做。这样子只需要请求一次,减少网络请求次数
- 缩小图片,那么就是靠压缩了,压缩png啊,svg啊,这些都可以去npmjs找相应的库,或者转换成webp格式,那么这里我以前也推荐过一个图形化的转换和压缩啊,这里再重提一下。web2jpg-online
- 懒加载图片,这块我直接粘贴作者的代码,并附上我的注释,简单的原理就是img图像的src不要设置(或者设置一个相同大小的灰色背景占位图),而是设置为
data-src属性为url地址,那么超过视口部分的,当要被加载到的时候,重新用js把src的值设置为data-src中的值,那么此时图像才会被请求
html文件此时长成这样子,把srcset、src这些都加上data-前缀,同时img的class里面加上lazy这个类,表示懒加载的图像。
<picture>
<source data-srcset="img/fish-and-chips-2x.webp 2x, img/fish-and-chips-1x.webp 1x" type="image/webp">
<source data-srcset="img/fish-and-chips-2x.jpg 2x, img/fish-and-chips-1x.jpg 1x" type="image/jpeg">
<img src="img/blank.png" data-src="img/fish-and-chips-1x.jpg" class="recipeImage lazy">
</picture>
js文件如下
(function(window, document){
"use strict";
// 懒加载的对象
var lazyLoader = {
lazyClass: "lazy", //类名就是lazy
images: null, //这是一个数组,存放着所有lazy类的图像,也就是所有需要懒加载的图像
processing: false, //处理状态,用于限制执行,如果懒加载程序正在执行,那么就不要再次执行了
throttle: 200, //节流时间,单位为毫秒,处理完之后过多少时间在处理
buffer: 50, //视口缓冲大小,用于加载视口边缘附近的图像,这样子在接近图像的时候就可以加载图像了,比如这里就是距离视口有50px的时候就可以加载图像了
/* 初始化懒加载程序 */
init: function(){
lazyLoader.images = [].slice.call(document.getElementsByClassName(lazyLoader.lazyClass)); //获取所有lazyClass的图像
lazyLoader.scanImages(); //扫描图像
document.addEventListener("scroll", lazyLoader.scanImages); //滚动事扫描图片
document.addEventListener("touchmove", lazyLoader.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){ //如果属性里面包含lazy这个值
if(lazyLoader.inViewport(lazyLoader.images[i])){ //并且这个图像是在视口+缓冲区内
lazyLoader.loadImage(lazyLoader.images[i]); //就可以加载这张图片了
}
}
}
lazyLoader.processing = false;
}, lazyLoader.throttle); // 这里就是节流了,指定超过时间
}
},
/* 核心部分——判断是不是在视口+缓冲区中 */
inViewport: function(img){
var top = ((document.body.scrollTop || document.documentElement.scrollTop) + window.innerHeight) + lazyLoader.buffer; //scrollTop就是当前页面顶部距离网页最顶层的距离,其中包括那些不可见的元素,||后面主要是为了兼容垃圾IE,然后再加上视口高度(包括滚动条)和buffer缓冲区的长度。
return img.offsetTop <= top; // 判断懒加载图像的offsetTop是不是比top小(这块的offsetTop应该并不是通用的?因为offsetTop是对最近的offsetParent的距离,可能本例的parent就是body,反正这块计算可能并不通用)
/*
*可以用这种方法,递归去获取top
function getTop(e) {
let T = e.offsetTop;
while (e = e.offsetParent) T += e.offsetTop;
return T;
}
*/
},
/* 真核心部分——加载图像 */
loadImage: function(img){
// 如果图片的父节点是picture,那么就可以
if(img.parentNode.tagName === "PICTURE"){
// 获取source元素
var sourceEl = img.parentNode.getElementsByTagName("source");
for(var i = 0; i < sourceEl.length; i++){
// 把data-srcset的属性赋值给srcset属性
var sourceSrcset = sourceEl[i].getAttribute("data-srcset");
if(sourceSrcset !== null){
sourceEl[i].setAttribute("srcset", sourceSrcset);
sourceEl[i].removeAttribute("data-srcset");
}
}
}
// img元素把data-src和data-srcset赋值给定影的元素,并且删除原有的data-属性
var imgSrc = img.getAttribute("data-src"),
imgSrcset = img.getAttribute("data-srcset");
if(imgSrc !== null){
img.setAttribute("src", imgSrc);
img.removeAttribute("data-src");
}
if(imgSrcset !== null){
img.setAttribute("srcset", imgSrcset);
img.removeAttribute("data-srcset");
}
// 别忘了删除这个lazy class
lazyLoader.removeClass(img, lazyLoader.lazyClass);
},
// 删除类,没啥好说的,就是操作字符串
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);
}
}
// 这块书作者写的,那时候没有join函数吗?直接 classArr.join(" ")
img.className = classArr.toString().replace(",", " ");
}
};
document.onreadystatechange = lazyLoader.init; // 当文档ready的时候初始化懒加载
})(window, document);
当然还有其他懒加载的方法,其中还包含html5的,只有添加一个属性就支持了,具体可以看我博文JS八股文-10-图片懒加载
字体
- 体积方面:woff2<woff<ttf,因为woff2本身就采用了压缩,所以首选woff2,没有也可以将ttf转换成woff2
- @font-face指定unicode-range属性,可以根据unicode范围来加载所需要的字体文件
简单小知识,本网站就直接挪用了B站的font-face,B站目前采用的是HarmonyOS字体,简单又不失优雅。比如下面是其中的一部分,这就是正常regular的字体,字重是400,font-display需要开启swap,表示首先显示回退文字,字体加载完后会切换成自定义的字体。src这块直接用bilibili的url,unicode-range这块包含所显示的,这块包含了大部分的中日韩字体。
@font-face {
font-family: 'HarmonyOS_Regular';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url('//s1.hdslb.com/bfs/static/jinkela/long/font/HarmonyOS_Regular.e.woff2') format('woff2');
unicode-range: U+30e0-6445;
}
更快的JS
- head中的
<script>标签可能阻塞渲染,放在body底部可以加快页面渲染速度(可以,我平时操作都是这样子的,但是理由不一样,我以为是放到底部等到dom渲染完成再执行脚本) - 如果可以管理脚本的执行,则使用async属性可以进一步提升性能
- 能用原生方法用原生,第三库在性能和体积上都会有点劣势,虽然在开发速度上会快一点
- requestAnimationFrame来设置js的动画,在渲染和绘制速度都快一点,甚至在这两点上比css过渡都快。
这块JS其实没有讲什么,提升的幅度不是很大吧
微调资源运输
- 压缩资源,之前讲过,有些压缩过的不需要再压缩了,比如图片文件jpg,png,字体文件woff、woff2,这些再压缩,体积也不会小,反而使得性能遍地。文本压缩使用Brotli,有着比gzip更好的表现。(本网站也开启了br压缩,nginx安装过程参考这篇博文,还是比较麻烦的,注意ubuntu下有些依赖库和yum名称不一样,还有.so放的位置也不一样)
- 使用Cache-Control头部为网站配置缓存行为,能够提高访问者回访网站时的性能
- 静态资源托管到CDN,可以缩短网站的总体加载时间(本网站采用腾讯云CDN加速,css、js、图片都提供16天的缓存,到期后会去自动拉取博客)
- 子资源完整性验证(这个下载某些文件的时候比较合适,提供MD5\SHA1\SHA256的校验)
- prefetch和preload资源提示可以加快速度,比如你的body末尾有个script脚本要加载,可以提前在head里面指定prefetch或者preload来预先加载资源,提前拿到,这样子加载速度就快了。
其他
-
HTTP2能够多路复用,也支持压缩头部了。因为HTTP2支持主动推送,所以HTTP1中的某些优化方式,比如捆绑资源、雪碧图和资源内联等组合方式,现在变成了反模式。HTTP2可以在你获取index.html的时候,直接把index.css推送给你,而不是获取完index.html的时候,解析到link标签,再一次去请求文件,甚至你在获取index.html 的时候,能够把所有的文件都给你推送过来(但不建议这么做,只是能做)
-
利用Service Worker来提升性能,SW可以拦截网络请求,并且缓存网络资源,比起浏览器缓存还能有着更快的表现,但是这个东西目前自己还用不到,编写起来也比较麻烦。
-
如何利用gulp来自动化处理,因为书本是15年写的,gulp在当时还是比较流行的,现在已经不用了,至少我是没有用过的,也没有接触过用过的项目,所以这个就不看了,前端有些东西注定会成为过去。






好细呀
哈哈,都是小结一波书上的,说实话看起来很多,但是我感觉还不够多,比如我想看到js里面用事件委托给父节点这种性能优化,可惜没有。更多是从文件层面和网络资源这方面做的优化
我之前也发现了B站的字体资源可以白嫖,哈哈
哈哈,确实,人人都在白嫖B站,叔叔亏麻了
我靠,带佬,看了你的网站,我发现之前看过你的网站,我还做过主题推荐呢,没想到到我这寒舍参观了,惊喜啊!https://szx.life/%E5%88%86%E4%BA%AB%E5%A5%BD%E7%9C%8B%E7%9A%84%E7%BD%91%E7%AB%99%E4%B8%BB%E9%A2%98/#6-thyuu