Web性能知识(三)
Web性能取决于:资源获取【网络I/O】 + 页面布局与渲染 【浏览器渲染原理】+ JavaScript执行【事件循环】
这是一篇讲解性能优化手段的文章,参考后续补上;内容都是自己的笔记
网络优化
预加载技术
Preload
<link rel="preload">
用于强制浏览器请求资源并且不会阻塞onload事件。
Preload用于提前加载当前页面所需的资源,例如脚本、图片之类
Prefetch
<link rel="prefetch"
用于暗示浏览器此资源可能需要,但是什么时候加载取决于浏览器。
Prefetch用于提前准备接下来访问的页面需要的资源,例如:在访问A页面时提前加载B页面的资源,导航到B页面时B页面加载更快
Preoload、Prefetch网络优先级
对于script,不同的位置以及是否是defer、async、blocking都影响着其网络优先级:
- blocking && 在第一张图片前,Medium
- blocking && 在第一张图片后,Low
- async/defer/插入的脚本,Lowest
对于Image:
- 可见且位于可视区域,Medium
- 不在可视区域,Lowest
- 布局完成后,低优先级且未加载的图片进入视口时会提升优先级
Preload的as属性给予的优先级与<link type>
一致:
<link rel="preload" as="style" href="xx.css">
,Highest- 都受到CSP约束
- 不带as的Preload等同于async XHR
Prefetch资源时跳转到其他页面时,该请求不会停止,且会在网络堆栈中保留5分钟
Preload Header不会等待浏览器扫描HTML发现需要的prelaod资源而是直接加载;与此同时使用Preload首部可能会触发HTTP/2 PUSH
页面加载优化
优化内容效率
- 基于文本的资源优化
- 预处理与环境特定优化
- 删除无关注释
- 简化CSS样式
- 删除无用的空格和制表符、未使用的代码
- 根据不同内容采取不同压缩算法
- 启用GZIP压缩CSS、HTML、JS
- 预处理与环境特定优化
- 图像优化
- 消除或替换图像
- CSS3实现渐变阴影等
- 网页字体代替图片字体
- 选择合适的格式
- 矢量图
- 适合包含几何图形的形状
- 缩放和分辨率无关
- 使用点、线、多边形来表示图像
- 光栅图
- 应用于包含大量不规则形状和复杂场景
- 对矩形格栅内的每个像素的值进行编码来表示图像
- 矢量图
- 优化矢量图(SVG)
- 缩减SVG文件(删除各种元数据)
- SVG文件应通过Gzip压缩来减小传输体积
- 优化光栅图
- 每个像素都包含了颜色和透明度信息,RGBA
- 图像压缩程序使用各种方法来减少每个像素所需的位数,以减小图像的文件大小
- 无损图像压缩 vs 有损图像压缩
- 图像格式上的差异源于压缩算法的差别
- 有损压缩:去掉某些像素数据
- 无损压缩:对像素数据进行压缩
- 消除或替换图像
优化图片
JPEG压缩算法
- 基线压缩算法
- 从上到下进行编码解码
- 渐进式压缩算法
- 多次扫描逐渐提高图像质量
- 网速差时给用户展示质量差的图像,提高感知性能
- 解码速度比基线JPEG慢2-3倍,小型图片上体积大于基线JPEG
- 无损压缩算法
- 通过移除EXIF数据、优化图像的霍夫曼表、重新扫描图像来优化图像
WebP压缩算法
- 有损压缩算法
- 体积比JPEG小25%-34%
- 无损压缩算法
- 比PNG小26%
优化手段
使用srcset提供HiDPI图像,允许浏览器为每个设备选择最佳图像
1
2
3
4<img srcset="paul-irish-320w.jpg,
paul-irish-640w.jpg 2x,
paul-irish-960w.jpg 3x"
src="paul-irish-960w.jpg" alt="Paul Irish cameo">图像精灵
- 不适用于HTTP/2,因为现在可在单个连接中多次请求
延迟加载非关键图像
- 加快首页加载速度
- 减少数据消耗、降低电池消耗
- Lazysizes
- 缺点:
- 屏幕阅读器等禁用script无法看到图片
- 滚动侦听器会对浏览器滚动性能产生影响(可能导致多次重绘)
避免
diplay: none
问题- 给图片设置
display:none
时,也会触发图像src
请求 - 使用
<picture>
、<img srcset>
来解决问题
- 给图片设置
图像上CDN、HTTP缓存
预加载关键图片
渐进式图像
优化JavaScript
存在的问题
- 网络:
- 仅发送用户所需代码
- 代码拆分,将代码拆分为关键、非关键代码
- 延迟加载非关键代码
- 源码压缩:uglifyJS压缩源码
- 压缩:Gzip
- 移除未使用的代码:tree shake、代码覆盖率检查
- 缓存
- 仅发送用户所需代码
- 解析/编译/执行时间/内存GC/长任务
PRPL(推送、渲染、预先缓存、延迟加载)
Tree Shaking
Code Splitting
存在的问题:JS文件过大,导致首次加载页面缓慢且部分模块更新时必须重新整体打包发布,未能更好利用缓存
拆分方式:
- Vendor拆分,将公共代码拆分出来【所有应用必须实现】
- 入口拆分,按应用程序的入口进行拆分
- 动态拆分,使用
import()
语句进行代码拆分【适用于SPA】
字体优化
字体加载顺序:
- 浏览器执行页面布局并确定需要使用何种字体
- 对于所需字体,浏览器确认是存在本地
- 对于不存在本地的字体,浏览器按照格式顺序进行加载
- 若浏览器不支持第一种格式,则加载第二种格式
- 若浏览器支持则下载字体
优化加载和渲染
加载存在的问题(延迟文本渲染):
- 浏览器请求HTML文档
- 浏览器解析HTML响应、构建DOM
- 浏览器发现 CSS、JS 以及其他资源并分派请求
- 浏览器在收到所有 CSS 内容后构建 CSSOM,然后将其与 DOM 树合并以构建渲染树。
- 在渲染树指示需要哪些字体变体在网页上渲染指定文本后,将分派字体请求。
- 浏览器执行布局并将内容绘制到屏幕上
- 如果字体尚不可用,浏览器可能不会渲染任何文本像素。
- 字体可用之后,浏览器将绘制文本像素。
解决方案:
- 使用字体unicode子集
- 压缩字体:GZIP
- 预加载字体:
<link rel="preload" as="font" href="">
font-display
- auto:使用用户代理的字体策略,多为block
- block:3s未加载则回退字体,字体加载后交换
- swap:首先用默认字体,字体加载后交换
- fallback:100ms内未加载字体则回退,3s内加载了字体则交换
- optional:100ms内未加载字体则回退
- FontLoading API:提供一种脚本编程接口来定义和操纵 CSS 字体,追踪其下载进度,以及替换其默认延迟下载行为
懒加载资源
延迟加载是指延迟加载页面中的非关键资源,例如:位于可视区域外的图片、视频等。
图像延迟加载
根据srcoll事件,判断元素是否进入可视区域【如何判断元素进入可视区域】
getBoundingClientRect
返回的是元素相对于视口的位置;加上scrollX/scrollY就是元素的在网页的位置。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
36document.addEventListener("DOMContentLoaded", function() {
let lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));
let active = false;
const lazyLoad = function() {
if (active === false) {
active = true; // 每200ms执行一次,可以用函数节流代替
setTimeout(function() {
lazyImages.forEach(function(lazyImage) {
if ((lazyImage.getBoundingClientRect().top <= window.innerHeight && lazyImage.getBoundingClientRect().bottom >= 0) && getComputedStyle(lazyImage).display !== "none") {
lazyImage.src = lazyImage.dataset.src;
lazyImage.srcset = lazyImage.dataset.srcset;
lazyImage.classList.remove("lazy");
lazyImages = lazyImages.filter(function(image) {
return image !== lazyImage;
});
if (lazyImages.length === 0) {
document.removeEventListener("scroll", lazyLoad);
window.removeEventListener("resize", lazyLoad);
window.removeEventListener("orientationchange", lazyLoad);
}
}
});
active = false;
}, 200);
}
};
document.addEventListener("scroll", lazyLoad);
window.addEventListener("resize", lazyLoad);
window.addEventListener("orientationchange", lazyLoad);
});
使用
Intersection Observer
API由浏览器提供的API,性能和效率更高
分离检测元素是否在可视区域的代码,开发者只需关注 元素进入/离开的业务
由于浏览器差异,部分旧的浏览器版本不支持(兼容性差)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23document.addEventListener("DOMContentLoaded", function() {
var lazyImages = [].slice.call(document.querySelectorAll("img.lazy"));
if ("IntersectionObserver" in window) {
let lazyImageObserver = new IntersectionObserver(function(entries, observer) {
entries.forEach(function(entry) {
if (entry.isIntersecting) {
let lazyImage = entry.target;
lazyImage.src = lazyImage.dataset.src;
lazyImage.srcset = lazyImage.dataset.srcset;
lazyImage.classList.remove("lazy");
lazyImageObserver.unobserve(lazyImage);
}
});
});
lazyImages.forEach(function(lazyImage) {
lazyImageObserver.observe(lazyImage);
});
} else {
// Possibly fall back to a more compatible method here
}
});
视频延迟加载
- 视频不自动播放
- 用视频替代动画gif
代码执行优化
requestAnimationFrame、requestIdleCallback
- requestAnimationFrame可以合适地调度动画,使渲染尽可能达到60fps
- requestIdleCallback利用空闲阶段(每一帧的空余时间或用户不活跃时)调度任务
requestIdleCallback
requestIdleCallback(myNonEssentialWork)
第一个参数为回调函数
该回调函数会收到一个deadline作为其实参
1
2
3
4
5
6function myNonEssentialWork(deadline) {
while(deadline.timeRemaining() > 0 && tasks.length > 0) // 若有空闲时间则调度任务
doWorkIfNeeded();
if (tasks.length > 0)
requestIdleCallback(anontherNonEssentialWork) // 上一个任务执行完毕,可调度其他任务(若还有时间)
}
第二个参数为options对象,{ timeout: xxx }
- 确保在所给定的时间内,该任务一定会执行(用于浏览器一直忙碌的情形)
- 该回调函数收到的deadline.timeout就是该参数
返回一个ID
- 可被取消,
window.cancelIdleCallback(handle)
- 可被取消,
requestAnimationFrame
requestAnimationFrame告诉浏览器在下次重绘前调用指定的回调函数更新动画。
业界常见性能优化手段
Google PageSpeed Insights Rules
- 消除阻塞渲染的JavaScript和CSS(最大限度减少网页上关键资源的数量并尽可能消除这些资源,减少下载关键字节数,优化关键路径长度)
- 优化JavaScript的使用(JavaScript资源会阻塞解析器,所以将其标记为async或通过专门的JavaScript代码进行添加。阻塞解析器的JavaScript强制浏览器等待CSSOM并暂停DOM的构建,继而大大延迟首次渲染的时间)
- 延迟解析JavaScript(延迟加载对构建首次渲染的可见内容无关紧要的脚本)
- 避免运行时间长的JavaScript
- 优化CSS的使用(CSS置于文档head标签内、避免使用CSS import、内联阻塞渲染的CSS)
Yahoo Best Practices for Speeding Up Your Web Site
- 减少http请求
- 使用CDN
- 利用浏览器缓存
- 压缩页面元素
- 样式表放在head
- JavaScript文件放在底部
- 避免CSS表达式
- JS、CSS放在外部文件中
- 减少CDN查询
- 压缩JavaScript
- 避免重定向
现代前端的优化策略
- 前端资源文件离线化
- 页面组件化并按需加载
- 预渲染提升感官性能