CSS性能优化基本思路和方法汇总

2020-03-10 17:37:50

本文我将从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

CSS属性名的声明顺序

声明顺序如下:

① 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选择器效率从高到低排序

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 上执行时,仅触发渲染层合并。

百度MIP官网横幅


以上,就是小宇关于CSS性能优化基本思路和方法汇总的分享,部分来自腾讯课堂Next学院笔记,部分整理自百度MIP官方文档。  

周末愉快,推荐一本算法入门书,没有任何代码哦,看图就可以了! 

我的第一本算法书封面

范❦淼℘
可以可以!
阿柒
有小姐姐在吗??加个好友呗!我以是小姐姐