共计 3201 个字符,预计需要花费 9 分钟才能阅读完成。
写了这么多 class,color,background,display…; 也许有时候会疑惑,怎么就显示在页面上,改变元素的样式。
本文简明介绍整个解析,匹配,渲染过程
css 描述
css 是 Cascading Style Sheets 的简写,是一种样式表语言。对应多种语法规则, 可以为 HTML 指定样式。
基本规则
图片来源。
css 规则由两部分组成:选择器 和 声明。上图中 element 对应的类选择器,紧接着声明(Declaration)。
每条声明由一个属性 (Property) 加冒号(:) 和 一个值 (value) 加分号(;) 组成。
CSS 解析
解析 html 或外部 css 文档,就是将文档转化成为有意义的结构,能够让代码理解。解析结果代表了文档结构的节点树,
解析分为词法分析 和 语法分析。
词法分析,也是编译原理中的术语,从左到右一个字符一个字符的读入源程序,对字符流进行扫描,根据构词规则识别单词,。这一过程可以使用 lex 等工具自动生成。
语法分析,主要任务是在词法分析的基础上,将单词序列组合成各类语法短语,如“程序”,“语句”,“表达式”
解析工作通常会被拆分为两个组件:
词法分析器,负责将输入流分解成有效的字符。
解析器,负责根据不同语言的语法规则来分析文档结构,最后构造出解析树。词法分析器知道如何去除不相关的字符,比如空格和换行
具体到 css 解析,因为它是上下文无关的语法,可以利用各种解析器进行解析。webkit 使用 Flex 和 Bison 解析器生成器,通过 css 语法文件自动创建解析器。解析器将 CSS 文件解析成 StyleSheet 对象,且每个对象都包含 CSS 规则。CSS 规则包含选择器和声明对象。
上图是 一个像素点的一生的 ppt 中选出图片,感兴趣可以看看演讲视频,非常直白,css 样式规则会被各种方式索引以便进行快速有效的查找。实现各个样式属性的 C ++ 类,比如 ppt 中的 BorderLeftColor 类,是在构建时 Python 脚本自动生成的。
具体到代码实现中,webkit 使用 CSSRuleSet 对象来保存 style rule,在一般规则需要创建时,调用 createStyleRule();
浏览器从右往左匹配选择器
在使用 css 选择器进行样式匹配时,尽量少用层级关系,因为这样可以减少选择器匹配的次数,提高 css 解析效率。
其实浏览器使用从右到左的解析顺序,同样提高了效率。通常在写 css 样式时,我们一贯的想法是,从左往右解析,从根节点开始,一层一层遍历匹配,直到所有的选择器都匹配上了。浏览器解析的顺序正好相反。
从右往左匹配的好处
简单回顾下本次话题的上下文,也就是浏览器对页面解析过程:
HTML parser 生成 Dom Tree
css parser 生成 style rules, 也就是 CSSOM tree。
Dom 与 CSSOM 匹配完成后,最后结合生成 render tree。
根据 render tree 开始 布局 …
根据这张图,可以对渲染过程大致了解。
回到浏览器匹配 css 规则上,如果只有一个选择器 对应匹配一个元素,从左往右匹配看似非常合理,但是正常情况是,一个 dom 节点,比如 <div class=”cls1 cls2 cls3″ id=’id1′></div>,可能对应了无数个 css 规则,没有上限,我可以在上面加非常多的样式。css 匹配效率的关键就是如何快速判断尽可能多的选择器并不能匹配。
先看看有多层嵌套的 css 规则。比如 #root .box .wrap i {}, 如果从左往右解析,最左边开始,直到最右边的选择器 i, dom 节点上根本没有 i 标签, 遍历到最后才排除 css 规则。
相反,从最右边的选择器部分开始匹配,如果不成功,整个匹配过程就可以立刻结束;成功了,继续往左,匹配父节点,跟树的深度成正比。所以浏览器的匹配方式,可以非常快速的排除大部分的选择器。
根据 2009 年在 Firefox 上做的测试,结论是仅仅从最右边的选择器开始检查,就可以排除 70% 的规则。快速除去 2 / 3 的 css 规则后,后面只用担心剩余的 1 /3。
样式作用在 DOM 元素上
从 css 文档被解析器解析完成,将数据保存在对象模型中,获取所有已解析的样式规则,结合浏览器提供的默认样式,计算出每个 DOM 元素最终的样式值。保存在 ComputedStyle 对象模型中,它是由样式属性和值形成的 map。并且 getComputedStyle 已经暴露出来,在 js 中通过 window.getComputedStyle,可以获取元素的最终样式。
共享 ComputedStyle
如果多个 element 的 computedStyle 不通过计算可以确认它们相等,那么这些 elements 只会计算一次样式,其余的直接共享该 ComputedStyle。
那些规则会共享 computedStyle(待验证):
该共享的 element 不能有 id 属性且 CSS 中还有该 id 的 StyleRule, 即使 StyleRule 与 element 不匹配
tagName 和 class 属性必须一样。
mappedAttribute 必须相同。
不能使用 sibling selector, 比如 :first-child, :last-child.
不能有 style 属性,哪怕 style 属性相同。他们也不会共享。
<p style=”color:red”>p1</p>
<p style=”color:red”>p2</p>
渲染
页面绘制到屏幕后,页面结构的改变也有可能导致渲染树重新计算,其中重排和重绘是最耗时的部分。
在页面的生命周期中,随时都有可能发生重排(Layout)和重绘(Painting)
重排(reflow)
当可见节点位置 及尺寸发生变化时都会发生重排,而且重排开销比重绘更大。
至少会有一次重排,发生在初始化页面布局的时候。
触发重排的几种情况:
添加或删除可见 dom 元素
元素位置改变
元素尺寸改变,
文字,内容,字体发生改变
页面初始化渲染 ….
重绘(repaints)
改变元素的外观属性(如 background-color, border-color, visibility),不影响整个布局,浏览器就会根据元素的新属性重新绘制。
重绘不会带来重新布局
层合成(composite)
DOM 树中每个节点的都对应一个 LayoutObject, 拥有相同的坐标空间的 LayoutObject, 属于同一渲染层(RenderLayers)。
渲染层保证元素按照正确顺序合成(composite), 正常展示元素的重叠以及元素透明等。
存在一些特殊情况,为满足指定条件的 LayoutObject 会拥有独立的渲染层,其他 layoutobject 则和第一个拥有渲染层的父元素公用一个。
利用 chrome Devtools 查看绘制过程。
打开 Chrome Devtools,按下 Esc 建 在出现的面板上 点击左上角三点。
选择 rendering, 转到对应标签。
打开后,页面中闪烁的绿色区域,表示这块需要重新绘制。当滚动侧边栏时,会出现整块绿色
总结
本文对 css 整个渲染过程进行简单介绍,试图把大量复杂知识点串在一起,至少可以让平时跟 css 打交道的我们,了解大致是个什么过程,同时也是对自己学习过程中的一次总结。对于里面涉及到的概念,都可以作为一个切入点,去好好研究。
参考链接
Things nobody ever taught me about CSS
缩小样式计算的范围并降低其复杂性
为什么浏览器读取 css 规则的顺序是从右到左
why-do-browsers-match-css-selectors-from-right-to-left
【Hello CSS】第一章 -CSS 的语法与工作流
编译原理之词法分析、语法分析、语义分析
How Browsers Work: Behind the scenes of modern web browsers
前端代码如何通过浏览器演化为屏幕显示的像素
Webkit CSS 引擎分析
简化绘制的复杂度、减小绘制区域
无线性能优化:Composite