首先我们要确定可以从什么方向对体验进行优化 影响网页体验有很多方面,不过一般方向无非下面这三大类
- 网络
- JS执行
- 页面渲染
关于网络优化
关于网络优化,我们要先了解发起网络请求的流程
- 查询对应的域名
- 加入请求队列
- 发起连接
- 发送请求
- 等待响应
- 下载内容
其中 4, 5, 6 步都是受客户端到服务器之间的网络影响,前端几乎没什么能优化的所以着重说下剩下的几个方面如何优化
DNS (影响小)
首先什么是 DNS 可以参考 Wikipedia 域名系统
这部分优化说白了就是让浏览器在拿到地址时就直接把对应的 IP 解析出来,不要在真正发起请求的时候再解析。
<link rel="dns-prefetch" href="https://fonts.googleapis.com/" />
请求队列
浏览器对于单个域名只能同时建立4~6个TCP连接(不同浏览器实现有差异)。
如果在 http/1.1
的情况下比如说我现在有六个大体积的图片正在加载,剩下一个影响布局的 JS 请求只能等待前面的任意一个请求完成才能发起,很明显这会增加布局完成的时间降低用户体验,但是在 HTTP/2
中则不存在这个问题,优化方面有下面这几个
使用 HTTP/2
这个需要服务端启用 HTTP/2 协议,浏览器会自动与服务端协商。如果服务端支持 HTTP/2,浏览器会优先使用这个协议。否则会回退到 HTTP/1.1。
域名分片
把原来耗时的请求例如图片、字体等放到另一个域名中。这是最常见的做法
例如谷歌就把字体等图片放到不同的域名中
将耗时的请求延后
确定哪些请求比较重要,先加载会影响页面布局的资源。
例如下面的 JS 标签 defer 属性可以让浏览器在 HTML 解析完成后再执行脚本,这样就不会阻塞页面渲染。但是注意,这个属性对模块脚本无效。
<script src="https://example.com/script.js" defer></script>
对于图片和 iframe 可以使用 loading="lazy"
属性来延迟加载,只有当进入视口时才会加载。
<img src="image.jpg" loading="lazy" alt="Lazy loaded image">
发起连接
思路同样是预连接
<link rel="preconnect" href="https://example.com" />
但是如果一个页面需要与许多第三方域建立连接,将它们全部预连接可能会适得其反。 比如一个页面有很多图片和脚本来自不同的域名,预连接所有这些域名可能会导致浏览器在加载页面时建立过多的连接,反而增加了初始加载时间。
下载内容
下载内容的优化主要是减少请求体积还有减少白屏时间。
减少请求体积
可以通过压缩等方式减少请求体积。不过这些优化一般打包工具会自动处理。
减少白屏时间
可以通过预加载、骨架屏等方式减少白屏时间。
关于 JS 执行优化
JS 执行的优化主要是减少执行时间和按需执行。
减少执行时间
- 对于重计算的场景,可以使用 Web Worker 将计算任务放到后台线程中执行,从而避免阻塞主线程。
- 对于频繁更新的场景,可以使用 requestAnimationFrame 来优化渲染性能。
- 对于复杂的计算,可以缓存计算结果,避免重复计算。
按需执行
- 可以通过动态导入、懒加载等方式实现按需执行。
import('./module.js').then(module => {
// 使用模块
});
- 对于不需要立即执行的脚本,可以使用
defer
或async
属性来延迟加载。
<script src="https://example.com/script.js" defer></script>
关于页面渲染优化
页面渲染的优化主要是减少重绘和重排。
减少重绘和重排
- 使用 CSS 动画代替 JavaScript 动画,CSS 动画通常更高效。
- 使用
will-change
属性来提示浏览器哪些元素将会发生变化,从而提前优化渲染。
.element {
will-change: transform;
}
- 合并多个样式变更,减少重排次数。
- 使用
requestAnimationFrame
来批量处理 DOM 更新,避免多次重排。
requestAnimationFrame(() => {
// 批量更新 DOM
element.style.transform = 'translateX(100px)';
element.style.opacity = '0.5';
});