一起来编写高性能 CSS

在开始之前

其实浏览器解析CSS 的速度非常快,所以今天所说的技巧带来的效能收益可能没有你想的那么显著。 相比撰写良好CSS 带来的优化,大部分时间我们应该聚焦在减少网路请求、减少JavaScript bundle size、图片最佳化等会非常显著影响页面效能的地方,或者从载入时间的角度提出优化技巧。

也因为CSS 不太会对性能产生「巨大」的影响,所以在撰写CSS StyleSheet 时应该始终以可读性与可维护性为原则。 不过不能因为不会对性能产生「巨大」的影响,就自顾自的乱写一通,觉得样式只要显示出来就好。

学会撰写对提升效能较有帮助的CSS,尤其是在开发大型专案时. 累积起来也可能有不少优化空间。 相反的,如果一直不管品质,也许累积起来在某些情况下也会成为效能的瓶颈。

那么,为什么不呢?

CSS 选择器

不同于我们阅读与撰写是从左至右的习惯,CSS Selector 的匹配是反过来的,也就是 从右向左进行

这也导致不同的Selector 写法之间的效性也存在着一些差异。

例如:

#ironman .article h2 {
/*     Some Style */
}

使用这个selector 需要先找到DOM 中所有的h2 元素,再filter 掉祖先元素不是.article 的,最后再filter 掉祖先元素不是#ironman 的元素。

这个selector 相比于直接用id selector 例如#ironman-article-h2 会需要花更多的时间才能生成render-tree。

不同 CSS Selector 的性能

  1. ID selector ID选择器
  2. Class Selector 类别选择器
  3. Element Selector 元素选择器
  4. General Sibling Combinator 兄弟选择器
  5. Child Combinator 子选择器
  6. Descendant Combinator 后代选择器
  7. Attribute Selector 属性选择器
  8. Pseudo Element/Class Selector 伪元素选择器

以上是常见的选择器,按照效能排列从上到下效能由高到低。 (注:ID Selector 与Class Selector 效能其实差异不大)

CSS Selector 优化Tips

我们现在已经知道CSS Selector 是「从右到左」查找,也介绍了单个选择器的效能比对,接着就可以来看看一些被推荐的CSS Best Practice 写法。

千万不要乱用万用字元- *

#ironman * {
  color: yellow;
  font-size: 12px;
}

因为是从右至左

(有学过SQL 的应该也知道SELECT * FROM 是很耗资料库效能的语法)

所以一个CSS 选择器的效能关键通常都在最右侧,这个最右侧的选择器又被称作「关键选择器」。

不是都用效能最好的 ID Selector 就好

前面有提到ID Selector 的效能是最好的,「欸!那就全部都用ID Selector 就好啦!」等等等,这样我们得对页面上所有元素都指定id attribute,况且id 还必须是唯一不能重复的识别码,你觉得现实中这可能吗? 嗯...看来是一个效能与最佳实践的取舍。

不过刚刚也有提到其实Class Selector 的效能不会比ID Selector 差太多,所以大部分的状况是建议使用Class Selector,而不是ID。 不然你想想,如果是采用元件化开发,会有重复使用元件的状况,那重复ID 反而成为了一个问题,虽然可以动态传入参数丢到id attribute 里,但并不是那么好维护。

ID Selector 适用于标识长久存在且唯一的元素,例如页面中的nav bar 或header,就适合使用ID Selector:

#navbar {
  background-color: green;
}

避免在ID 或是Class Selector 前面限定元素类型

div#navbar {}
p.ironman {}

我以前并不觉得这样的写法有问题,不过我们用CSS「从右至左」匹配元素的思路来分析看看。

首先看到#navbar 而先去找到id 为navbar 的元素,注意,id 是不会重复的,所以照理来说已经找到我们要的元素了,不过因为发现左边有一个div,所以还是要额外再去分析它,当然最后结果跟直接用#navbar 去匹配是一样的,反而还额外做了一次判断。

Child Combinator 子选择器优先于Descendant Combinator 后代选择器

如果今天你想找包在div 里面的p tag(一层的父子关系),那么div>p 这个写法的效能会比div a 这个写法还要好。

一样从右边的选择器开始看,两者都会先去搜寻页面上的所有p tag,不过前者只会检查一个层级,看看这些p tag 的父层有没有是div tag 的。 后者则会一层层往上查找,父层没有就往父层的父层查找。 相比起来前者耗费的工会少许多。

善用CSS 属性继承的特性

父元件的CSS Properties 是会继承到子元件上的,所以如果子元件要沿用父元件的样式,建议使用继承的特性,而不是在每个子选择器都设定一次。

/* This is not good */
#ironman {}
#ironman > .kyle { color: blue; }
#ironman > .mo { color: blue; }

/* This is better */
#ironman { color: blue; }

非必要的情况,减少使用昂贵的Attribute

所谓昂贵的属性指的是一些需要浏览器进行操作或计算的属性,它们需要耗费更多的浏览器性能。 当页面重绘时,这些属性可能会降低浏览器的渲染效能。 例如:

  • :nth-child
  • 筛选
  • 不透明度
  • 盒子阴影
  • 边界半径

不过其实这些都是很常见且很重要的属性呢,所以并不是叫大家不要使用,而是建议当你的需求有其他方式可以达成时,可以思考一下是不是真的需要使用这些属性。

要用CSS 来写动画时,通常会用transition property 来指定哪些 CSS properties 会被转场效果影响。

举例来说你希望指定class name 为ironman 的元素在hover 时scale 会变1.5 倍,你希望这个过程是有平滑的动画的,你可能会这样写:

.ironman {
    scale: 1;
    transition: all .2s ease-in;
}

.ironman:hover {
    scale: 1.5;
}

但其实如果你已经明确知道transition 的对象是scale,你应该这样写

/* 不要用 all */
.ironman {
    scale: 1;
    transition: scale .2s ease-in;
}

transition: all 的写法会让浏览器在特定情况下会额外去做一些判断,相较于specific 的写法效能会比较差。

小结

有没有觉得要写出高效能的CSS 也是十分不容易的啊!我自己觉得初学者比较容易误解的地方是CSS 的匹配顺序并不是像一般阅读习惯的从左到右,而是「由右至左」,理解这个道理后再思考不同Selector 的性能就会豁然开朗了!

参考文章:
https://domhabersack.com/high-performance-css
https://domhabersack.com/css-hierarchy-matching
https://iter01.com/13467.html
https://www.smashingmagazine.com/2016/12/gpu-animation-doing-it-right/
https://web.dev/extract-critical-css/
https://web.dev/first-contentful-paint/

打赏
评论区
头像
    头像

      

    涨知识了

    头像
    橙梓
      

    竟然是从右到左的解析,有点意外,长知识了

文章目录