本文我将从CSS的命名规则,选择器效率,渲染顺序,渲染次数以及动画等方面,结合实战,提供CSS性能优化的基本思路和方法,提高开发和维护效率。
1.关注点分离原则
在HTML中使用CSS,可采用行间,嵌入和外链三种形式。好,现在让我们忘记行间和嵌入,只关注一个目标,在整个网站/应用上,只使用一个外链CSS,这样会强制开发人员思考:
1.1 专注于CSS样式
1.2 提高样式的复用度
1.3 怎样在一个CSS中更好地区分不同页面的样式,如何写注释,如何划分区域,如何模块化CSS
1.4 极大地简化维护逻辑
1.5 降低HTTP请求数量,事实上,在HTTP2.0,这样的降低依然是有意义的,它能提升加载的稳定性,降低TCP协议丢包可能造成的数据阻塞影响,更加充分地利用缓存
1.6 可否将把这个过程工程化,自动化,借助第三方工具或者自己写脚本?
问题:将所有网页的CSS样式集合在一起,是不是浪费带宽?
通常不会,或者轻微增加的冗余量带来的增益大于带宽消耗。
如果每个页面的代码冗余度过高,这个问题从侧面反映了网站/应用可能没有统一的风格,CSS样式的复用度太低,太多页面采用了独一无二的CSS。如果你的网站/应用足够大,将它拆分为不同频道,不同模块,模块间共享CSS,是更好的方法。
如果你使用了CSS框架或组件,这里并不强制一定要把框架或组件里的CSS,打包进来,通常情况下,直接引用公用的第三方库,比如BootCDN是更优的选择。道理也很简单,比如用户在网站A上加载了PureCSS,你的网站也使用了PureCSS,两者都引用自同一第三方CDN库,那么自然实现了两个网站间的缓存共享。
2. BEM命名法
作为面向对象OOP思维在CSS上的实践,使用BEM命名法,不仅仅可以见名知意,更可以帮助前端开发者在命名过程中,建立起对网页/应用结构的整体概念,思考:
2.1 HTML结构是否足够清晰,是否使用不必要的嵌套
2.2 如何将网页拆解分块,如何更好地提取这些块间的公共结构
2.3 这些块在整个网站/应用中具备怎样的价值,能否复用,能否扩展
2.4 更大限度地利用CSS继承
2.5 配合SASS或者LESS等书写,能好地表达出层次关系,利用混淆、循环和变量,实现开发代码的精简
问题:如何使用BEM命名呢?
(1)BEM定义:
BEM分别对应Block块,Element元素和Modifier修饰符。
块:描述页面上的纯文本和布局,名称唯一
元素:标识页面上的一个元素,名称唯一
修饰符:一个块或者一个元素的一种属性,代表这个块或者这个元素在外观或者行为上的改变
{
block: 'page',
content: {
block: 'head',
content: [
{block: 'menu', content: ''},
{
elem: 'colum',
content: {block, 'logo'}
}
]
}
}
上述展示了一个块和元素相互嵌套的对象模型,这种结构被称为 BEM模型。
JSON格式描述的对象模型 + 模版引擎,可以输出HTML
(2)命名原则:
① 一个块(一个元素)必须有唯一的名字
② 不使用标签选择器,因为上下文无关
③ 不使用级联选择器
④ 如果Javascript需要操作的对象唯一,使用id选择器,命名为“js_{id名}”
⑤ 如果Javascript需要操作一类对象,使用class选择器
(3)命名规则:
块名__元素名--修饰符
3. CSS属性书写规则
早期的Chrome,会将开发者随意书写的CSS属性名,按照一定顺序重新排列,重新排列后的顺序,一定程度上符合浏览器的渲染规则,思考:
3.1 如何高效地描述一个块或元素的样式,而不遗漏和重复定义属性
3.2 从CSS1.0到4.0,按照保障不错位,兼顾功能,提升体验的兼容性要求,逐层增加属性
3.3 尽量避免使用简写,因为简写中,你未定义的属性存在默认值,这意味着,你可能仅仅想指定顶部边距,而同时定义了四个边距。给代码的复用和维护带来困难。此外,简写例如border,会触发浏览器重排,如果您仅仅是想改变border-style和border-radius,那么分开声明显示是更好的选择。
(1)问题:为什么要规定CSS属性的书写规则:
让CSS属性的书写顺序,符合浏览器渲染页面的顺序,减少不必要的重排和重绘:
① HTML -> DOM树
② CSS -> CSSDOM树
③ DOM树 + CSSDOM树 -> Render树
④ 布局 Render树
⑤ 绘制 Render树
⑥ 重排Reflow
⑦ 重绘Repaint
根据这样的顺序,我们应尽量将 影响文档流占位 的属性名提前,比如position,display,尽量避免使用行间样式,或者使用js操作这些属性。
需要提醒的是:现代浏览器已经相对智能:
a. ①②③④⑤是可以分步进行的,浏览器可以先渲染一部分内容,等待另一部分内容下载完成再行渲染,减少白屏时间。前端也可以通过手动懒加载DOM的方式,来强制浏览器优先渲染首屏内容;
b. 一次性异步渲染:当修改元素多个Style属性时,浏览器不会每次都立即渲染,而将多次修改后的结果合并后,一次渲染到位。为充分利用浏览器这种特性,应尽量减少使用计算属性,包括与自适应无关系的不必要的依赖窗口尺寸的属性名,使用JS设置style时,属性值减少使用依赖其他属性的计算结果,如果一定要依赖,请先计算完成。用变量存储计算结果,并将这个变量赋值给style属性。
c. 尽量减少重排和重绘,我会在第5小节CSS动画,重点介绍CSS重排和重绘的优化。
(2)问题:CSS属性的声明顺序?
请大家参考 mdo 的CSS编码规范:
https://codeguide.bootcss.com/#css-syntax
声明顺序如下:
① Positioning 位置
② Box model 盒模型
③ Typographic 排版
④ Visual 视觉
⑤ Misc 杂项
具体每个属性名的推荐顺序,可以参考推特的规范:
https://github.com/twitter/recess/blob/master/lib/lint/strict-property-order.js
如果你需要使用具体的标识位置的属性名,请尽量遵守 上右下左 这样的顺序。
4. CSS选择器优先级
从CSS1.0到CSS3.0,CSS选择器飞速进化,CSS4.0更是极大地丰富了伪类选择器的种类,包括否定伪类、匹配伪类,关系伪类,表单限制伪类,可读可写伪类。
然而,越高级的选择器,意味着更高的性能开销,并且大量的关系和伪类选择器的使用,常常会给项目的维护带来困难。
根据CSS-Tricks中题为《 Efficiently Rendering CSS》说明,CSS选择器效率从高到低排序:
① ID选择器(#myid)
② 类选择器(.myclassname)
③ 标签选择器(div,h1,p)
④ 相邻选择器(h1+p)
⑤ 子选择器/直接选择器(ul > li)
⑥ 后代选择器(li a)
⑦ 通配符选择器(*)
⑧ 属性选择器(a[rel]="external")
⑨ 伪类选择器(a:hover,li:nth-child)
问题:选择器路径是怎样的?
① 浏览器解读选择器,遵循原则:从选择器的右边到左边读取
② 路径链越短,效率越高,建议选择器的层级最多不要超过4层
问题:如何优化CSS选择器?
① 避免使用通用选择器,尤其是避免关键选择器是通配选择器的情况。
.content * {color:red}
由于选择器的读取顺序是从右边到左边,所以,此时,浏览器会匹配文档中所有的元素后,再分别向上逐级匹配class为content的元素,直到文档的根节点。因此其匹配开销是非常大的。
② 避免使用低效率的选择器限制高效率的选择器:
避免使用标签或类选择器限制ID选择器,避免使用标签选择器限制class选择器
③ 尽量直接使用类选择器:
避免使用多层标签选择,避免使用子选择器
④ 尽量使用继承:
可以继承的属性,直接声明到父级
5 CSS动画
动画必备属性:transform
transform本意是变形,变换之意,在CSS中使用该属性可对元素进行移动(translate)、旋转(rotate)、缩放(scale)和倾斜(skew)等效果,transform动画性能优良。
问题:transition和animation的区别:
transition:
① 需要借助交互触发:
:hover :active :checked :focus
:add/remove class
② 只能定义第一帧和最后一帧的样式
animation:
① 即可自动,也可以借助交互
② 可以控制多帧
③ 可以控制暂停播放
问题:如何优化CSS动画的性能
(1)60FPS与设备刷新率
目前大多数设备的刷新率为60FPS(目前新手机的刷新率为144FPS),即每秒60帧。如果在页面中有个一个动画或渐变效果或者用户正在滚动页面,那么浏览器渲染动画或页面的每一帧的速率,也需要跟设备屏幕的刷新率保持一致,即每一帧要在16毫秒(1S/60 = 16.66ms)之内完成。
如果无法完成,由于帧率的下降,会导致内容在屏幕上抖动。
此现象通常称为卡顿,会对用户体验产生负面影响。
(2)浏览器渲染:
① CSS图层
浏览器在渲染一个页面时,会将页面分为多个图层,图层有大有小,每个图层上有一个或多个节点。
如果图层中某个元素需要重绘,那么整个图层都需要重绘。
② 渲染过程
浏览器的渲染过程就是将页面转换成像素显示到屏幕上,大致步骤如下:
·JavaScript操作:使用JavaScript来实现交互操作,添加元素,切换显示隐藏,JavaScript + CSS Animations + Transitions + Web Animation
·Style样式计算:根据CSS选择器,获取每个元素匹配的CSS样式,计算元素的最终样式
·Layout布局:该过程计算元素占据的空间的阿晓机器屏幕的位置,网页的布局模式意味着一个元素可能影响其他元素,例如<body>元素的宽度一般会影响其子元素的宽度以及树中各处的节点。
·Paint绘制:本质上就是填充像素的过程,包括绘制文字、颜色、图像、边框和阴影等,这就是绘制元素的所有的可视效果。
·Composite渲染层合并:在每个层上完成绘制过程之后,浏览器会将所有层按照合理的顺序合并成一个图层,然后显示在屏幕上。
想要提高动画的性能,需要做的是减少浏览器在动画运行时,所需要做的工作。当CSS在进行动画时,其不同属性值引起的改变,重新渲染可能会有三种执行路径:
A layout -> paint -> composite(重排)
B paint -> composite(重绘)
C composite
其中C动画性能最高,所以,我们在使用动画的时候,需要考虑使用什么属性,尽量减少执行路径。
(3)动画属性
① 布局类(layout)
② 绘制类(paint)
③ 合成类(composite)
(4)重排(reflow)
由元素的布局类属性改变触发的行为过程,称之为重排(reflow),又成为重新布局(relayout)。
当某个节点reflow时,会重新计算节点的尺寸和位置,还可能会引起其它节点的reflow。
该系列属性的改变,会执行A路径进行重新渲染,所以性能最差。这充分说明,重排会引起重绘。
问题:哪些属性会触发重排?
盒子模型的相关属性会触发重布局:
·display
·padding
·margin
·width
·height
·min-height
·border
·border-width
定位属性及浮动也会触发重布局:
·position
·top
·bottom
·left
·right
·float
·clear
改变节点内部文字结构也会触发重布局:
·font-family
·font-size
·font-weight
·line-height
·text-align
·vertical-align
·white-space
·overflow
·overflow-y
(5)重绘(repaint)
由绘制类属性改变触发节点重新绘制其可视效果的过程,称之为重绘(repaint)。
该系列属性的改变,会执行路径B,所以性能一般。
问题:哪些属性会触发重绘?
·color
·border-style
·border-radius
·visibility
·text-decoration
·background
·background-image
·background-position
·background-repeat
·background-size
·outline
·outline-color
·outline-style
·outline-width
·box-shadow
上面的属性由于不会修改节点的大小和位置,因此不会触发重排,其只是改变了节点内部的渲染效果,所以只会重绘以下的步骤。
(6)composite(合成)
问题:哪些属性会触发合成?
·transform
`opacity
上面的属性,会执行路径C,所以性能最佳。
(7)CSS动画的优化技巧
① 减少动画元素
减少动画元素,是动画性能优化中首先要完成的。通过审查页面动画DOM元素结构,去除不必要的动画元素,减少元素的数量,相应地会减少页面布局和绘制的时间。
② 尽量使用fixed、absoute定位
对于动画元素,尽量使用fixed、absolute定位方式,避免影响到其他节点重排。
③ 尽量只改变transform和opacity
能用transform和opacity优先使用,其属性的改变不会发生重排和重绘。
如进行位移操作,可以使用translate来实现。渐变效果,可以使用opacity属性来实现。
④ 恰当开启硬件加速效果
对动画元素应用 transform: translate3d(0, 0, 0)、transform: translateZ(0)、will-change:transform等来开启硬件加速,当然这也意味着更高的内存消耗。
6 百度MIP CSS优化策略
来自百度MIP官方的优化建议:
(1)所有静态资源需要标明尺寸
我们需要为页面上的声明的(广告、图片、视频、音频播放器)等静态资源,标明尺寸,早期,百度建议为每个img标签,书写width和height属性。随着自适应布局的兴起,为图片指定宽度或高度,让未指定的部分自适应成为趋势。
然而,再加载图片等静态资源之前,由于盒模型大小未知,浏览器必须等待资源加载完成后,或者meta信息读取完成后,再重复重排,重绘,渲染页面。在CSS中为资源指定大小,是一个良好的习惯。同时对于优化程度较高的场景,我们可以依赖后端,根据场景即时返回附带大小信息的静态资源。节省浏览器计算的时间。
(2)不允许任何机制阻止页面渲染,建议使用 inline 的 CSS
与本文第一点不同的是,百度MIP建议使用 内联CSS,因为外链CSS的加载会阻塞页面渲染。我们推荐在进行MIP开发时,使用官方策略,因为官方已经准备好了完备的组件库,不建议我们额外引入外链CSS,以便最大限度利用百度MIP缓存。非MIP开发时,仍采用原方式。
(3)只允许 GPU 加速的动画
与5 CSS动画 优化策略一致,百度MIP规范更为严格:只允许声明 transforms 和 opacity 来完成动画效果,当动画能在 GPU 上执行时,仅触发渲染层合并。
以上,就是小宇关于CSS性能优化基本思路和方法汇总的分享,部分来自腾讯课堂Next学院笔记,部分整理自百度MIP官方文档。
周末愉快,推荐一本算法入门书,没有任何代码哦,看图就可以了!