# 前言
上一篇我们已经围绕 “网络层面” 探索页面性能优化的方案,接下来本篇围绕 “浏览器渲染层面” 继续开展探索。正文开始前,我们思考如下问题:
- 浏览器渲染页面会经过哪几个关键环节?“渲染层面” 的优化从哪几方面着手?
- “渲染层面” 的性能优化方案会有哪些?
# 渲染关键环节
页面渲染过程详见:《性能优化之 10 分钟看懂浏览器的渲染过程及优化》
# 优化原则
我们了解 “页面渲染关键环节” 后,便可知晓影响页面渲染性能的因素主要是静态资源:HTML、CSS、JS、图片等。因此 “渲染层面” 的性能优化方案主要就是围绕静态资源展开探索,其方案制定可围绕下面 2 个原则展开:
- 尽可能减少资源个数
- 尽可能减少资源体积大小
# 优化方案
# HTML 优化
# 1. 减少文件大小(压缩、精简)
- 压缩处理 HTML,减小 HTML 体积
- 精简 HTML:
- 尽量减少 HMTL 嵌套、iframe/table 使用(table 标签比其他 html 标签占用更多字节,导致下载时间长,占用服务器更多的流量资源)
- 删除多余的空格、换行符、缩进和不必要注释
- 删除冗余标签和属性
- …
# 2.DOM 优化
- 控制 DOM 大小:
- 合理的业务逻辑拆分
- 先加载可视区,其他延迟加载(懒加载)
- 减少 DOM 操作:尽可能对 DOM 操作统一逻辑处理,或是使用虚拟 DOM(借鉴 vue/react),再插入到真实 DOM(减少重排重绘)
# CSS 优化
# 1. 减少资源请求个数
- 合并多个 CSS 样式文件
- 按需加载样式
# 2. 减少文件大小(压缩)
- 压缩处理 CSS 文件
- 在线压缩,例如 CSS Minify
- webpack 压缩插件:css-minimizer-webpack-plugin
# 3. 减少文件大小(编码优化)
- 位置放在
<head>
里,尽早地进行样式解析,构建 CSSOM 树 - 简化 CSS 选择器(选择器优先级:!important > 内联 > id > class | 属性 | 伪类 > 标签 | 伪元素)
- 尽可能减少样式层级数(选择器嵌套)
- 少用标签选择器,尽量选择高优先级的 id/class/ 属性 / 伪类选择器代替
- 少用通配符 *,只对需要修改样式的元素进行选择
- 关注可继承属性,避免重复定义和匹配
可继承属性:
- 所有元素可继承:visibility 和 cursor。
- 内联元素可继承:letter-spacing、word-spacing、white-space、line-height、color、font、 font-family、font-size、font-style、font-variant、font-weight、text- decoration、text-transform、direction。
- 块元素可继承:text-indent 和 text-align。
- 列表元素可继承:list-style、list-style-type、list-style-position、list-style-image。
- 表格元素可继承:border-collapse。
总结:CSS 继承特性主要是指文本方面的继承,而关于与盒模型相关的属性不支持继承。
- 优化 CSS 编码风格,尽可能减少重排重绘
- 元素显隐操作频繁,可设置 visibility: hidden,让元素占位不变,不会触发重排,仅触发重绘
- 已知图片的宽高,样式可先设置 width、height,避免图片下载后整个页面发生重排
- 避免使用 CSS 表达式
- 避免频繁设置同一 div 样式,可进行一次性更改
- 动画使用绝对定位(脱离文档流)定位,避免触发重排
- …
重排重绘具体可参考:github.com/GGXXMM/FE-K…
- 精简 CSS,从而减少 CSS 文件大小
- 使用缩写语句,如 margin-top/bottom,直接用 margin
- 删除不必要的单位,如 0px,直接写 0
- 删除过多分号
- 删除空格和注释
- …
# JS 优化
# 1. 减少文件请求个数
- 合并多个 JS 脚本文件
- 首屏渲染暂不需要且体积大的脚本可进行按需加载(通过 script 动态创建加载)
- 合理使用缓存
- 业务中:
- 非敏感固定的数据可使用本地存储
localStorage
/sessionStorage
/IndexedDB
等 - 合理缓存
DOM
对象等
- 非敏感固定的数据可使用本地存储
- 构建打包(比如 webpack):
- 第三方模块缓存:hard-source-webpack-plugin、DLL 动态链接库
loader
开启cache
缓存- …
- 业务中:
# 2. 减少文件大小
- 压缩处理:在线压缩 / Webpack 插件(uglify/gzip)
- 构建打包减小 bundle 体积
- 开启 TreeShaking,剔除 Dead Code
- 剥离第三方依赖,webpack 配置 externals
- 提取公共模块:webpack4.x+ 配置 optimization.splitChunks(相当于 webpack 旧版本的 CommonsChunkPlugin)
# 3. 编码优化
# 加载时
- 位置放
<body>
底部,让 JS 不阻塞 HTML 和 CSS 的解析 - 合理选择加载模式(减少重排重绘)
// 1. 正常模式 | |
<script src="index.js"></script> | |
// 2. async 模式(异步执行) | |
<script async src="index.js"></script> | |
// 3. defer 模式(延迟执行) | |
<script defer src="index.js"></script> |
一般当我们的脚本与 DOM 元素和其它脚本之间的依赖关系不强时,我们会选用 async;当脚本依赖于 DOM 元素时,我们会选用 defer。(合理选择 script 加载模式,可以有效地提升性能)
# 运行时
DOM 操作优化
- 合并多次 DOM 操作,批量执行,减少重排重绘
- 大数据量渲染优化:分页加载 / 虚拟列表 virtualList
函数优化
- 获取 DOM 元素,尽量使用 id 选择器
- 尽量避免使用 eval
- 使用事件节流 / 防抖函数
- 使用事件委托
JS 动画优化
- 避免添加大量的 JS 动画
- 简单动画尽量使用 CSS3 动画
- 复杂动画尽量使用 Canvas 动画
- 合理使用 requestAnimationFrame 动画代替 setTimeout、setInterval
requestAnimationFrame 按照浏览器的刷新率(60Hz 左右)来调动动画帧,从而实现更加流畅和高性能的动画效果。
执行优化
- 合理拆分执行任务
- 大数据计算,可使用 Web Worker 开启独立于主线程的并行计算
…
# 图片优化
# 1. 减少图片网络请求
- 雪碧图 CSS sprite
- 小图标使用 web font 字体代替
- 小体积图片使用 base64 编码
- 阴影、简单动画图效可用 CSS3 代替
- 图片懒加载 / 预加载
# 2. 减小图片大小
- 图片压缩,可选择如下方式:
# 3. 合理使用图片格式
简介和特性 | 是否支持透明 | 支持颜色种数 | 压缩方式 | 浏览器兼容性 | 适用场景 | |
---|---|---|---|---|---|---|
jpg | – 最常见、应用最广泛的图片格式 – 体积一般,通常小于 png, gif 等格式 | 不支持透明 | 约 1600 万种颜色 | 有损压缩 | 几乎所有浏览器都支持 | 呈现色彩丰富的图片,比如大背景图、轮播图或 Banner 图等 |
png8 | – 一种无损压缩的高保真的图片格式 – 8 位的 png,体积较大 | 支持透明 | 256 种(2^8) | 无损压缩 | 几乎所有浏览器都支持 | 呈现小图片,比如小 Logo、颜色简单对比强烈小图标 |
png24 | – 一种无损压缩的高保真的图片格式 – 24 位的 png,体积较大 | 不支持透明 | 约 1600 万种颜色 | 无损压缩 | 几乎所有浏览器都支持 | 呈现颜色较多的图片,比如背景图 |
png32 | – 一种无损压缩的高保真的图片格式 – 32 位的 png,体积大 | 支持半透明(8 位透明通道) | 2^32 种 | 无损压缩 | 几乎所有浏览器都支持 | 呈现色彩丰富高清图片,比如海报 |
svg | – 矢量图,任意缩放不影响清晰度 – 体积视内容而定 | 支持设置透明度 | RGB/RGBA/ 十六进制设置颜色 | 支持有损和无损压缩 | Chrome 4 (2010 年 1 月发布)以上版本支持 caniuse.com/svg | 适用任意缩放不失真的场景 |
gif | – 支持动态图片 – 压缩率较高 – 体积较小 | 支持透明 | 256 种(2^8) | 无损压缩 | Chrome 58(2017 年 6 月发布)以上版本支持 caniuse.com/gif | 适用于色彩较少的动图 |
webp | – 支持动态图片 – 压缩率较高 – 体积较小 | 支持透明 | 约 1600 万种颜色 | 有损和无损压缩 | Chrome 32(2014 年 1 月发布)以上版本支持,兼容性不太好 caniuse.com/webp | 兼容性要求不高的多种图片格式场景 |
# 4. 图片加载优化
- 懒加载
图片列表一般采用懒加载进行按需加载,滚屏时当图片已将出现在可视区域的时候进行加载。(有效地减轻服务器批量加载图片的压力)
let imgList = [...document.querySelectorAll('img')]; | |
let len = imgList.length; | |
const lazyLoad = (function(){ | |
let count = 0; | |
return function() { | |
let deleteIndexList = []; | |
imgList.forEach((img,index)=> { | |
let rec = img.getBoundingClientRect(); | |
if(rec.top < window.innerHeight) {// 元素出现在可视区(window.innerHeight:可视窗口的高度) | |
img.src = img.dataset.src; // 将 data-src 设置成图片 src | |
deleteIndexList.push(index); | |
count++; | |
if(count === len) {// 当图片都加载完,移除滚动监听事件 | |
document.removeEventListener('scroll', lazyLoad) | |
} | |
} | |
}) | |
imgList = imgList.filter((img, index)=> !deleteIndexList.includes(index)) | |
} | |
})() | |
// 节流函数 | |
const throttle = function(fn, timing = 500) { | |
let prev = 0 | |
return function() { | |
let now = +new Date(); | |
if(now - prev > timing) { | |
prev = now; | |
fn.apply(this, arguments) | |
} | |
} | |
} | |
// 滚动监听加上节流控制 | |
const _throttle = throttle(lazyLoad) | |
document.addEventListener('scroll', _throttle) |
- 预加载
预加载 preload,在大图片加载完成前先加载小的 loading,用于提升用户体验。(该优化思想不仅可以用于图片加载,也能用于异步请求、html 标签预加载)
// 创建 img 图片元素 | |
const myImage = (function(){ | |
let imgNode = document.createElement('img') | |
document.body.appendChild( imgNode ) | |
return { | |
setSrc: function(src) { | |
imgNode.src = src | |
} | |
} | |
})() | |
/** | |
* 预加载 | |
*/ | |
const preLoad = (function(){ | |
let img = new Image(); | |
img.onload = function() { | |
myImage.setSrc( this.src )//this 指向 img | |
} | |
return { | |
setImg: function(src) { | |
myImage.setSrc('./img/loading.gif') | |
img.src = src | |
} | |
} | |
})() | |
preLoad.setImg('./img/bg_gaoqing.jpeg') |
- 渐进式加载
具体实现可参考:性能优化之渐进式图片加载
# 5. 响应式图片,随窗口大小变化
- CSS 媒体查询 @media 设置响应式
.bg { | |
/* 正常(未缩小屏幕)加载大尺寸图片 */ | |
background-image: url('img_flowers.jpg'); | |
} | |
/* 当屏幕宽度小于 400 */ | |
@media screen and (max-width: 400px) { | |
.bg { | |
background-image: url('img_smallflower.jpg'); | |
} | |
} |
- srcset 属性设置响应式
<picture> | |
<source srcset="img_smallflower.jpg" media="(max-width: 400px)"> | |
<img src="img_flowers.jpg" alt="Flowers"> | |
</picture> |