Web页面全链路性能优化指南
跟着动画来学习TCP三次握手和四次挥手
浏览器渲染原理
进程与线程
浏览器有多种进程,其中最重要的5种进程如下
- 浏览器进程 负责页面展示 用户交互 子进程管理 提供存储等
- 渲染进程 每个页面都有一个单独的渲染进程,用于渲染页面 包含 webworker线程
- 网络进程 主要处理网络资源加载 (HTML CSS JS IMAGE AJAX等)
- GPU进程 3D绘制 提高性能
- 插件进程 chrome 插件 每个插件占用一个进程。
输入url到页面展示完整过程。
Chrome 性能优化相关工具
Coverage(覆盖率)
Lighthouse
Network(网络)
- 正在排队:网络请求队列的排队时间
- 已停止:阻塞住用于处理其他事情的时间
- DNS查找:用于DNS解析IP地址的时间
- 初始连接:创建TCP连接时间
- SSL:用于SSL协商的时间
- 已发送请求:用于发送请求的时间
- 等待中:请求发出至接收响应的时间也可以理解为服务端处理请求的时间
-
网络请求的优先级
浏览器会根据资源的类型决定优先请求哪些资源,优先级高的请求能够优先被加载。
不同资源类型的优先级排序如下 最高:html、style
- 高:font、fetch、script
- 低:image、track
网络页性能优化
网络优化策略
减少HTTP请求数
合并JS、合并CSS、合理内嵌JS和CSS、使用雪碧图使用HTTP缓存
使用协商缓存可以减少数据传输,当不需要更新数据时可通知客户端直接使用本地缓存。
使用强制缓存可以不走网络请求,直接走本地缓存数据来加载资源。使用HTTP/2.0
HTTP/2.0会将所有以:开头的请求头做一个映射表,然后使用hpack进行压缩,使用这种方式会使请求头更小。
服务器可主动推送数据给客户端。
HTTP/2.0使用同一个TCP连接来发送数据,他把多个请求通过二进制分贞层实现了分贞,然后把数据传输给服务器。也叫多路复用,多个请求复用同一个TCP连接。避免重定向
301、302 重定向会降低响应速度使用dns-prefetch
DNS请求虽然占用的带宽较少,但会有很高的延迟,由其在移动端网络会更加明显。
使用dns-prefetch可以对网站中使用到的域名提前进行解析。提高资源加载速度。
通过dns预解析技术可以很好的降低延迟,在访问以图片为主的移动端网站时,使用DNS预解析的情意中下页面加载时间可以减少5%。
使用域名分片
在HTTP/1.1中,一个域名同时最多创建6个TCP连接,将资源放在多个域名下可提高请求的并发数CDN
静态资源全上CDN,CDN能非常有效的加快网站静态资源的访问速度。压缩
gzip压缩、html压缩、js压缩、css压缩、图片压缩使用contenthash
contenthash可以根据文件内容在文件名中加hash,可用于浏览器缓存文件,当文件没有改变时便直接取本地缓存数据合理使用 preload prefetch
preload预加载、prefetch空闲时间加载
两者都不会阻塞onload事件,prefetch 会在页面空闲时候再进行加载,是提前预加载之后可能要用到的资源,不一定是当前页面使用的,preload 预加载的是当前页面的资源。浏览器渲染优化策略
关键渲染路径
当通过JS或者其他任意方式修改DOM后,浏览器会进入如下流程
【JS通过API修改DOM】>【计算样式】>【布局(重排)】>【绘制(重绘)】>【合成】
Reflow 重排:重排在Chrome Performance中叫做布局,通常添加或删除元素、修改元素大小、移动元素位置、获取位置信息都会触发页面的重排,因为重排可能会改变元素的大小位置等信息,这样的改变会影响到页面大量其它元素的大小位置信息,会耗费掉大量的性能,所以在实际应用中我们应该尽可能的减少重排
Repaint 重绘:重绘在Chrome Performance中叫做绘制,通常样式改变但没有影响位置时会触发重绘操作,重绘性能还好,但我们也需要尽量减少重绘,如果需要做一些动画,我们尽量使用CSS3动画,CSS3动画只需要在初始化时绘制一次,之后的动画都不会触发重绘操作。强制同步布局问题
在同一个函数内,修改元素后又获取元素的位置时会触发强制同步布局,影响渲染性能
强制同步布局会使js强制将【计算样式】和【布局(重排)】操作提前到当前函数任务中,这样会导致每次运行时执行一次【计算样式】和【重排】,这样一定会影响页面渲染性能,而正常情况下【计算样式】和【重排】操作会在函数结束后统一执行。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<article id="article"></article>
<script>
const domArticle = document.querySelector('#article')
// const { offsetTop } = domArticle
function reflow () {
const domH1 = document.createElement('h1')
domH1.innerHTML = 'h1'
domArticle.appendChild(domH1)
/**
* 强制同步布局
* 在修改元素后又获取元素的位置时会触发强制同步布局,影响渲染性能
* 解决办法是采用读写分离的原则,同一个函数内只读、只写
*/
const { offsetTop } = domArticle
console.log(offsetTop)
}
window.onload = () => {
for (let i = 0; i < 10; i++) {
reflow()
}
}
</script>
</body>
</html>
如何减少重排与重绘
- 脱离文档流(绝对定位、固定定位),脱离文档流的元素进行重排不会影响到其他元素。
- 图片渲染时增加宽高属性,宽高固定后,图片不会根据内容动态改变高度,便不会触发重排。
- 尽量用CSS3动画,CSS3动画能最大程度减少重排与重绘。
使用will-change: transform;将元素独立为一个单独的图层。(定位、透明、transform、clip都会产生独立图层)
静态文件优化策略
图片格式
jpeg:适合色彩丰富的图、Banner图。不适合:图形文字、图标、不支持透明度。
png:适合纯色、透明、图标,支持纯透明和半透明。不适合色彩丰富图片,因为无损储存会导致储存体积大于jpeg
gif:适合动画、可以动的图标。支持纯透明但不支持半透明,不适合色彩丰富的图片。
埋点信息通常也会使用gif发送,因为1x1的gif图发送的网络请求比普通的get请求要小一些。
webp:支持纯透明和半透明,可以保证图片质量和较小的体积,适合Chrome和移动端浏览器。不适合其他浏览器。
svg:矢量格式,大小非常小,但渲染成本过高,适合小且色彩单一的图标。图片优化
减少图片资源的尺寸和大小,节约用户流量
- 设置alt=”xxx”属性,图像无法显示时会显示alt内容
- 图片懒加载, loading=”lazy”为原生,建议使用IntersectionObserver自己做懒加载
- 不同环境加载不同尺寸和像素的图片srcset与sizes的使用。
- 采用渐进式加载 先加载占位图,然后加载模糊小图,最后加载真正清晰的图
- 使用Base64URL 减少图片请求数
-
HTML优化
语义化HTML,代码简洁清晰,利于SEO,便于开发维护。
- 减少HTML嵌套关系,减少DOM节点数量。
- 提前声明字符编码,让浏览器快速确定如何渲染网页内容
- 删除多余空格、空行、注释、无用属性
- 减少iframe,子iframe会阻塞父级的onload事件。可以使用js动态给iframe赋值,就能解决这个问题。
-
CSS优化
减少伪类选择器,减少选择器层数、减少通配符选择器、减少正则选择器
- 避免css表达式background-color: expression(…)
- 删除空格、空行、注释、减少无意义的单位、css压缩
- css外链,能走缓存
添加媒体字段,只加载有效的css文件
<link rel="stylesheet" href="./small.css" media="screen and (max-width:600px)" />
<link rel="stylesheet" href="./big.css" media="screen and (min-width:601px)"/>
使用css contain属性,能控制对应元素是否根据子集元素的改变进行重排
-
JS优化
通过script的async、defer属性异步加载,不阻塞DOM渲染
- 减少DOM操作,缓存访问过的元素。
- 不直接操作真实DOM,可以先修改,然后一次性应用到DOM上。(虚拟DOM、DOM碎片节点)
- 使用webworker解决复杂运算,避免复杂运算阻塞主线程,webworker线程位于渲染进程
- 图片懒加载,使用IntersectionObserver实现
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<style>
img {
height: 200px;
display: block;
}
</style>
<title>Document</title>
</head>
<body>
<img src="./loading.gif" data-src="./01.jpg" />
<img src="./loading.gif" data-src="./02.jpg" />
<img src="./loading.gif" data-src="./03.jpg" />
<img src="./loading.gif" data-src="./04.jpg" />
<img src="./loading.gif" data-src="./05.jpg" />
<img src="./loading.gif" data-src="./06.jpg" />
<img src="./loading.gif" data-src="./07.jpg" />
<img src="./loading.gif" data-src="./08.jpg" />
<img src="./loading.gif" data-src="./09.jpg" />
<img src="./loading.gif" data-src="./10.jpg" />
<script>
const intersectionObserver = new IntersectionObserver((changes) => {
changes.forEach((item, index) => {
if (item.intersectionRatio > 0) {
intersectionObserver.unobserve(item.target)
item.target.src = item.target.dataset.src
}
})
});
const domImgList = document.querySelectorAll("img");
domImgList.forEach((domImg) => intersectionObserver.observe(domImg));
</script>
</body>
</html>
- 虚拟滚动
- 使用requestAnimationFrame来做动画,使用requestIdleCallback来进行空闲时的任务处理
- 尽量避免使用eval,性能差。
- 使用事件委托,能减少事件绑定个数。事件越多性能越差。
- 尽量使用canvas、css3动画。
- 通过chrome覆盖率(Coverage)工具排查代码中未使用过的代码并将其删除
通过chrome性能(Performance)工具查看每个函数的执行性能并优化
字体优化
FOUT(Flash of Unstyled Text)等待一段时间,如果没加载完成,先显示默认。加载 后再进行切换。
FOIT(F1ash of Invisib1e Text) 字体加载完毕后显示,加载超时降级系统字体(白 屏)<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
@font-face {
font-family: 'hagan';
src: url('./font.ttc');
font-display: swap;
/* b1ock 35 内不显示,如果没加载完毕用默认的 */
/* swap 显示老字体 在替换*/
/* fa11back 缩短不显示时间,如果没加载完毕用默认的,和b1ock类似*
/* optional 替换可能用字体 可能不替换*/
}
article {
font-family: hagan;
}
</style>
<title>Document</title>
</head>
<body>
<article>ABC abc</article>
</body>
</html>
浏览器存储优化策略
Cookie
cookie在过期之前一直有效,最大储存大小为4k,限制字段个数,不适合大量的数据储存,每次请求会携带cookie,主要用来做身份校验。
优化方式:需要合理设置cookie有效期
- 根据不同子域划分cookie来减少cookie传输
静态资源域名和cookie域名采用不同域名,避免静态资源请求携带cookie。
LocalStorage
SessionStorage
IndexDB
其他优化策略
关键资源个数越多,首次页面加载时间就会越长
- 关键资源的大小,内容越小下载时间越短。
- 优化白屏,合理使用内联css、js
- 预渲染,打包时进行预渲染,生成静态HTML文件,用户访问时直接返回静态HTML。
服务端渲染同构,加速首屏速度(耗费服务端资源),有利于SEO优化。首屏使用服务端渲染,后续交互使用客户端渲染。
使用PWA提高用户体验
webapp用户体验差的一大原因是不能离线访问。用户粘性低的一大原因是无法保存入口,PWA就是为了解决webapp的用户体验问题而诞生的。使用PWA能令站点拥有快速、可靠、安全等特性。
Web App Manifest 将网站添加到电脑桌面、手机桌面,类似Native的体验。
- Service Worker 配合Cache API,能做到离线缓存各种内容。
- Push API 配合 Notification API,能做到类似Native的消息推送与实时提醒。
- App Shell 配合 App Skeleton,能做App壳与骨架屏