gtavcss原理

html-css09

gtavcss原理,第1张

探究 CSS 解析原理

吃早饭的时候,同事随意问了一句:你知道 CSS 是怎么解析的吗?我一头雾水。对哦,作为前端,每天都在与 CSS 打交道,我竟然忽视了最基本的原理。

一、浏览器渲染

开篇,我们还是不厌其烦的回顾一下浏览器的渲染过程,先上图:

正如上图所展示的,我们浏览器渲染过程分为了两条主线:

其一,HTML Parser 生成的 DOM 树;

其二,CSS Parser 生成的 Style Rules ;

在这之后,DOM 树与 Style Rules 会生成一个新的对象,也就是我们常说的 Render Tree 渲染树,结合 Layout 绘制在屏幕上,从而展现出来。

本文的重点也就集中在第二条分支上,我们来探究一下 CSS 解析原理。

二、Webkit CSS 解析器

浏览器 CSS 模块负责 CSS 脚本解析,并为每个 Element 计算出样式。CSS 模块虽小,但是计算量大,设计不好往往成为浏览器性能的瓶颈。

CSS 模块在实现上有几个特点:CSS 对象众多(颗粒小而多),计算频繁(为每个 Element 计算样式)。这些特性决定了 webkit 在实现 CSS 引擎上采取的设计,算法。如何高效的计算样式是浏览器内核的重点也是难点。

先来看一张图:

Webkit 使用 Flex 和 Bison 解析生成器从 CSS 语法文件中自动生成解析器。

它们都是将每个 CSS 文件解析为样式表对象,每个对象包含 CSS 规则,CSS 规则对象包含选择器和声明对象,以及其他一些符合 CSS 语法的对象,下图可能会比较明了:

Webkit 使用了自动代码生成工具生成了相应的代码,也就是说词法分析和语法分析这部分代码是自动生成的,而 Webkit 中实现的 CallBack 函数就是在 CSSParser 中。

CSS 的一些解析功能的入口也在此处,它们会调用 lex , parse 等生成代码。相对的,生成代码中需要的 CallBack 也需要在这里实现。

举例来说,现在我们来看其中一个回调函数的实现,createStyleRule(),该函数将在一般性的规则需要被建立的时候调用,代码如下:

CSSRule* CSSParser::createStyleRule(CSSSelector* selector) { CSSStyleRule* rule = 0 if (selector) { rule = new CSSStyleRule(styleElement) m_parsedStyleObjects.append(rule) rule->setSelector(sinkFloatingSelector(selector)) rule->setDeclaration(new CSSMutableStyleDeclaration(rule, parsedProperties, numParsedProperties)) } clearProperties() return rule }

从该函数的实现可以很清楚的看到,解析器达到某条件需要创建一个 CSSStyleRule 的时候将调用该函数,该函数的功能是创建一个 CSSStyleRule ,并将其添加已解析的样式对象列表 m_parsedStyleObjects 中去,这里的对象就是指的 Rule 。

那么如此一来,经过这样一番解析后,作为输入的样式表中的所有 Style Rule 将被转化为 Webkit 的内部模型对象 CSSStyleRule 对象,存储在 m_parsedStyleObjects 中,它是一个 Vector。

但是我们解析所要的结果是什么?

通过调用 CSSStyleSheet 的 parseString 函数,将上述 CSS 解析过程启动,解析完一遍后,把 Rule 都存储在对应的 CSSStyleSheet 对象中;

由于目前规则依然是不易于处理的,还需要将之转换成 CSSRuleSet。也就是将所有的纯样式规则存储在对应的集合当中,这种集合的抽象就是 CSSRuleSet;

CSSRuleSet 提供了一个 addRulesFromSheet 方法,能将 CSSStyleSheet 中的 rule 转换为 CSSRuleSet 中的 rule ;

基于这些个 CSSRuleSet 来决定每个页面中的元素的样式;

这里描述了大致过程,深入阅读可以查看如下链接:

Webkit CSS 引擎分析CSS 样式表解析过程Webkit CSS实现

三、CSS 选择器解析顺序

可能很多同学都知道排版引擎解析 CSS 选择器时是 从右往左 解析,这是为什么呢?

1.HTML 经过解析生成 DOM Tree(这个我们比较熟悉);而在 CSS 解析完毕后,需要将解析的结果与 DOM Tree 的内容一起进行分析建立一棵 Render Tree,最终用来进行绘图。Render Tree 中的元素(WebKit 中称为「renderers」,Firefox 下为「frames」)与 DOM 元素相对应,但非一一对应:一个 DOM 元素可能会对应多个 renderer,如文本折行后,不同的「行」会成为 render tree 种不同的 renderer。也有的 DOM 元素被 Render Tree 完全无视,比如 display:none 的元素。

2.在建立 Render Tree 时(WebKit 中的「Attachment」过程),浏览器就要为每个 DOM Tree 中的元素根据 CSS 的解析结果(Style Rules)来确定生成怎样的 renderer。对于每个 DOM 元素,必须在所有 Style Rules 中找到符合的 selector 并将对应的规则进行合并。选择器的「解析」实际是在这里执行的,在遍历 DOM Tree 时,从 Style Rules 中去寻找对应的 selector。

3.因为所有样式规则可能数量很大,而且绝大多数不会匹配到当前的 DOM 元素(因为数量很大所以一般会建立规则索引树),所以有一个快速的方法来判断「这个 selector 不匹配当前元素」就是极其重要的。

4.如果正向解析,例如「div div p em」,我们首先就要检查当前元素到 html 的整条路径,找到最上层的 div,再往下找,如果遇到不匹配就必须回到最上层那个 div,往下再去匹配选择器中的第一个 div,回溯若干次才能确定匹配与否,效率很低。

对于上述描述,我们先有个大概的认知。接下来我们来看这样一个例子,参考地址:

<div> <div class="jartto"> <p><span>111 </span></p> <p><span>222 </span></p> <p><span>333 </span></p> <p><span class='yellow'>444 </span></p> </div></div>

CSS 选择器:

div >div.jartto p span.yellow{ color:yellow}

对于上述例子,如果按从左到右的方式进行查找:

1.先找到所有 div 节点;

2.在 div 节点内找到所有的子 div ,并且是 class = “jartto”;

3.然后再依次匹配 p span.yellow 等情况;

4.遇到不匹配的情况,就必须回溯到一开始搜索的 div 或者 p 节点,然后去搜索下个节点,重复这样的过程。

这样的搜索过程对于一个只是匹配很少节点的选择器来说,效率是极低的,因为我们花费了大量的时间在回溯匹配不符合规则的节点。

如果换个思路,我们一开始过滤出跟目标节点最符合的集合出来,再在这个集合进行搜索,大大降低了搜索空间。来看看从右到左来解析选择器:

1.首先就查找到 的元素;

2.紧接着我们判断这些节点中的前兄弟节点是否符合 P 这个规则,这样就又减少了集合的元素,只有符合当前的子规则才会匹配再上一条子规则。

结果显而易见了,众所周知,在 DOM 树中一个元素可能有若干子元素,如果每一个都去判断一下显然性能太差。而一个子元素只有一个父元素,所以找起来非常方便。

试想一下,如果采用从左至右的方式读取 CSS 规则,那么大多数规则读到最后(最右)才会发现是不匹配的,这样会做费时耗能,最后有很多都是无用的;而如果采取从右向左的方式,那么只要发现最右边选择器不匹配,就可以直接舍弃了,避免了许多无效匹配。

浏览器 CSS 匹配核心算法的规则是以从右向左方式匹配节点的。这样做是为了减少无效匹配次数,从而匹配快、性能更优。

深入阅读,请移步:

jQuery 源码解析CSS 选择器从右向左的匹配规则CSS 选择器

四、CSS 语法解析过程

CSS 样式表解析过程中讲解的很细致,这里我们只看 CSS 语法解释器,大致过程如下:

1.先创建 CSSStyleSheet 对象。将 CSSStyleSheet 对象的指针存储到 CSSParser 对象中。

2.CSSParser 识别出一个 simple-selector ,形如 “div” 或者 “.class”。创建一个 CSSParserSelector 对象。

3.CSSParser 识别出一个关系符和另一个 simple-selecotr ,那么修改之前创建的 simple-selecotr, 创建组合关系符。

4.循环第3步直至碰到逗号或者左大括号。

5.如果碰到逗号,那么取出 CSSParser 的 reuse vector,然后将堆栈尾部的 CSSParserSelector 对象弹出存入 Vecotr 中,最后跳转至第2步。如果碰到左大括号,那么跳转至第6步。

6.识别属性名称,将属性名称的 hash 值压入解释器堆栈。

7.识别属性值,创建 CSSParserValue 对象,并将 CSSParserValue 对象存入解释器堆栈。

8.将属性名称和属性值弹出栈,创建 CSSProperty 对象。并将 CSSProperty 对象存入 CSSParser 成员变量m_parsedProperties 中。

9.如果识别处属性名称,那么转至第6步。如果识别右大括号,那么转至第10步。

10.将 reuse vector 从堆栈中弹出,并创建 CSSStyleRule 对象。CSSStyleRule 对象的选择符就是 reuse vector, 样式值就是 CSSParser 的成员变量 m_parsedProperties 。

11.把 CSSStyleRule 添加到 CSSStyleSheet 中。

12.清空 CSSParser 内部缓存结果。

13.如果没有内容了,那么结束。否则跳转值第2步。

五、内联样式如何解析?

通过上文的了解,我们知道,当 CSS Parser 解析完 CSS 脚本后,会生成 CSSStyleSheetList ,他保存在Document 对象上。为了更快的计算样式,必须对这些 CSSStyleSheetList 进行重新组织。

计算样式就是从 CSSStyleSheetList 中找出所有匹配相应元素的 property-value 对。匹配会通过CSSSelector 来验证,同时需要满足层叠规则。将所有的 declaration 中的 property 组织成一个大的数组。数组中的每一项纪录了这个 property 的selector,property 的值,权重(层叠规则)。

可能类似如下的表现:

p >a { color : red background-color:black} a { color : yellow} div { margin : 1px}

重新组织之后的数组数据为(weight我只是表示了他们之间的相对大小,并非实际值。)

selector property weight 1, a color:yellow 1 2, p >a color:red 2 3, p >a background-color:black 2 4, div margin:1px 3

好了,到这里,我们来解决上述问题:

首先,要明确,内敛样式只是 CSS 三种加载方式之一;

其次,浏览器解析分为两个分支,HTML Parser 和 CSS Parser,两个 Parser 各司其职,各尽其责;

最后,不同的 CSS 加载方式产生的 Style rule ,通过权重来确定谁覆盖谁;

到这里就不难理解了,对浏览器来说,内联样式与其他的加载样式方式唯一的区别就是权重不同。

深入了解,请阅读Webkit CSS 引擎分析

六、何谓 computedStyle ?

到这里,你以为完了?Too young too simple, sometimes naive!

浏览器还有一个非常棒的策略,在特定情况下,浏览器会共享 computedStyle,网页中能共享的标签非常多,所以能极大的提升执行效率!如果能共享,那就不需要执行匹配算法了,执行效率自然非常高。

也就是说:如果两个或多个 element 的 computedStyle 不通过计算可以确认他们相等,那么这些 computedStyle 相等的 elements 只会计算一次样式,其余的仅仅共享该 computedStyle 。

那么有哪些规则会共享 computedStyle 呢?

该共享的 element 不能有 id 属性且 CSS 中还有该 id 的 StyleRule,哪怕该 StyleRule 与 Element 不匹配。

tagName 和 class 属性必须一样

mappedAttribute 必须相等

不能使用 sibling selector,譬如:first-child, :last-selector, + selector

不能有 style 属性。哪怕 style 属性相等,他们也不共享

当然,知道了共享 computedStyle 的规则,那么反面我们也就了解了:不会共享 computedStyle 的规则,这里就不展开讨论了。

深入了解,请参考:Webkit CSS 引擎分析 - 高效执行的 CSS 脚本

七、眼见为实

如上图,我们可以看到不同的 CSS 选择器的组合,解析速度也会受到不同的影响,你还会轻视 CSS 解析原理吗?

感兴趣的同学可以参考这里:speed/validity selectors test for frameworks

八、有何收获?

1.使用 id selector 非常的高效。在使用 id selector 的时候需要注意一点:因为 id 是唯一的,所以不需要既指定 id 又指定 tagName:

Badp#id1 {color:red} Good #id1 {color:red}

当然,你非要这么写也没有什么问题,但这会增加 CSS 编译与解析时间,实在是不值当。

2.避免深层次的 node ,譬如:

Bad div >div >div >p {color:red} Good p-class{color:red}

3.慎用 ChildSelector ;

4.不到万不得已,不要使用 attribute selector,如:p[att1=”val1”]。这样的匹配非常慢。更不要这样写:p[id=”id1”]。这样将 id selector 退化成 attribute selector。

Bad p[id="id1"]{color:red} p[class="class1"]{color:red} Good #id1{color:red} .class1{color:red}

5.理解依赖继承,如果某些属性可以继承,那么自然没有必要在写一遍;

6.规范真的很重要,不仅仅是可读性,也许会影响你的页面性能。这里推荐一个CSS 规范,可以参考一下。

更多资源

CSS 解析顺序优先级详细探索简单剖析 CSS 的解析规则

24个金币已到账

金币可兑换现金

立即提现

子宫肌瘤怎么办?告诉你一个调理方法!直达病灶!

觉得 首先要清楚每个浏览器不同之处 bug 之类的

然后 还可以用一些 hack 来针对不同的浏览器 进行单独调整

IE 6 是最烦的 ie和 遨游 效果基本相同 ff 和 op 差不多

总之你慢慢来么 断点调试css代码

一下收集的一些资料 你看看

浏览器的不兼容,大家肯定都是深恶痛绝的,往往我们只是去做修补,却忘了更重要的事情,那就是追溯根源,避免类似的不兼容再次出现。在下不才,归纳几点html编码要素,望能指点各位:

1.文字本身的大小不兼容。同样是font-size:14px的宋体文字,在不同浏览器下占的空间是不一样的,ie下实际占高16px,下留白3px,ff下实际占高17px,上留白1px,下留白3px,opera下就更不一样了。解决方案:给文字设定 line-height 。确保所有文字都有默认的 line-height 值。这点很重要,在高度上我们不能容忍1px 的差异。

2.ff下容器高度限定,即容器定义了height之后,容器边框的外形就确定了,不会被内容撑大,而ie下是会被内容撑大,高度限定失效。所以不要轻易给容器定义height。

3.还讨论内容撑破容器问题,横向上的。如果float 容器未定义宽度,ff下内容会尽可能撑开容器宽度,ie下则会优先考虑内容折行。故,内容可能撑破的浮动容器需要定义width。

小实验:有兴趣大家可以看看这段实验。在不同浏览器下分别测试以下各项代码。

a.<div style=”border:1px solid redheight:10px”></div> b.<div style=”border:1px solid redwidth:10px”></div>

c.<div style=”border:1px solid redfloat:left”></div> d.<div style=”border:1px solid redoverflow:hidden”></div>

上面的代码在不同浏览器中是不一样的,实验起源于对小height 值div 的运用,<div style=”height:10pxoverflow:hidden”></div>,小height 值要配合overflow:hidden一起使用。实验好玩而已,想说明的是,浏览器对容器的边界解释是大不相同的,容器内容的影响结果各不相同。

4.浮动的清除,ff下不清除浮动是不行的。

纠正大家一个误区,遇到不兼容就说ff烂是不对的,其实更多时候是ie的奇怪表现让我们无所适从。以下列出ie6的种种劣迹。

5.最被痛恨的,double-margin bug。ie6下给浮动容器定义margin-left 或者margin-right 实际效果是数值的2倍。解决方案,给浮动容器定义display:inline。

6.mirrormargin bug,当外层元素内有float元素时,外层元素如定义margin-top:14px,将自动生成margin-bottom:14px。padding也会出现类似问题,都是ie6下的特产,该类bug 出现的情况较为复杂,远不只这一种出现条件,还没系统整理。解决方案:外层元素设定border 或 设定float。

引申:ff 和ie 下对容器的margin-bottom,padding-bottom的解释有时不一致,似乎与之相关。

7.吞吃现象,限于篇幅,我就不展开了。还是ie6,上下两个div,上面的div设置背景,却发现下面没有设置背景的div 也有了背景,这就是吞吃现象。对应上面的背景吞吃现象,还有滚动下边框缺失的现象。解决方案:使用zoom:1。这个zoom好象是专门为解决ie6 bug而生的。

8.注释也能产生bug~~~“多出来的一只猪。”这是前人总结这个bug使用的文案,ie6的这个bug 下,大家会在页面看到猪字出现两遍,重复的内容量因注释的多少而变。解决方案:用“<!–[if !IE]>picRotate start <![endif]–>”方法写注释。

9.<li/>里加 float <div/>,这是一个典型的,棘手的兼容问题,希望引起大家正视 ,给li 不同的属性会有不同的解释效果,ff下的解释稍可理解,ie6下的解释会让你摸不着头脑,由于问题的复杂性,将另起一文专门讨论该问题。在《ul使用心得》一文里有相关成果,却没给出问题解决的过程。

10.使用了“float:leftdisplay:inline”的ul的奇怪表现。可以看出这句css是针对ie6下的double margin bug 而加上的display:inline,这也是我的css体系里的重要一环,在《ul使用心得》一文中有相关阐述。而这句css用在ul上会让你痛苦不堪。点到为止,这里不能多说哈。

11.img下的留白,大家看这段代码有啥问题:

<div>

<img src=”” mce_src=”” />

</div>

把div的border打开,你发现图片底部不是紧贴着容器底部的,是img后面的空白字符造成,要消除必须这样写

<div>

<img src=”” mce_src=”” /></div>

后面两个标签要紧挨着。ie7下这个bug 依然存在。解决方案:给img设定 display:block。

12.失去line-height。<div style=”line-height:20px”><img />文字</div>,很遗憾,在ie6下单行文字 line-height 效果消失了。。。,原因是<img />这个inline-block元素和inline元素写在一起了。解决方案:让img 和文字都 float起来。

引申:大家知道img 的align 有 text-top,middle,absmiddle啊什么的,你可以尝试去调整img 和文字让他们在ie和ff下能一致,你会发现怎么调都不会让你满意。索性让img 和文字都 float起来,用margin 调整。

13.链接的hover状态。a:hover img{width:300px} 我们想让鼠标hover时,链接里包含的图片宽度变化,可惜在ie6下无效,ie7、ff下有效。

14.非链接的hover状态。div:hover{} 这样的样式ie6是不认的,在ie7、ff下才有效果。

15.block化的a链接,其内套absolute层,absolute层内放置img,ie下,鼠标点击img不会有链接效果,ff、op下正常。

想不出来了,以后想到再加吧。上面的诸多问题如果你掌握了其中奥妙,90%的不兼容问题不需要另起css hack的。

16.无法彻底清除的float。如果让ul下的li具有了float属性,如何clear浮动的li呢?<ul><li class=”c”><></ul> 或者 <ul><li><div>class=”c”></div><></ul>或者 <ul><li><><div>class=”c”></div></ul>或者 <ul><li><></ul><div>class=”c”></div> 或者上述的组合?这个问题,我无法给出解答。下面有个例子与此相关

<!DOCTYPE html PUBLIC -//W3C//DTD XHTML 1.0 Transitional//EN http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd>

<style type=text/css>

.c{clear:bothoverflow:hidden+overflow:visible}

.bd{border:1px solid red}

ul.ex{list-style:none}

ul.ex li{float:leftborder:1px solid green}

</style>

<ul class=ex>

<li>sfsdfsfdf<>

<li>sfsdfsfdf<>

</ul>

<div class=c></div>

<div class=bd style=margin-top:19px>sfsdfsfdf</div>

请在ie下 测试,仅仅将 margin-top:19px 改为margin-top:20px 你发现什么了?要素:doctype必须有,ie6、ie7下margin-top:19px还好好的,margin-top:20px 就出问题了,无法解释。。。大家还可以将 clear 层换不同的位置测试。

解决方案:给ul 属性zoom:1 (给li 加zoom:1 没用)

引申:clear层应该单独使用。也许你为了节省代码把clear属性直接放到下面的一个内容层,这样有问题,不仅仅是ff和op下失去margin效果,ie下某些margin值也会失效

<div style=”background:redfloat:left”>dd</div>

<div style=”clear:bothmargin-top:18pxbackground:green”>ff</div>

17.ie下overflow:hidden对其下的绝对层position:absolute或者相对层position:relative无效。解决方案:给overflow:hidden加position:relative或者position:absolute。另,ie6支持overflow-x或者overflow-y的特性,ie7、ff不支持。

18.ie6下严重的bug,float元素如没定义宽度,内部如有div定义了height或zoom:1,这个div就会占满一整行,即使你给了宽度。float元素如果作为布局用或复杂的容器,都要给个宽度的。

19.ie6下的bug,绝对定位的div下包含相对定位的div,如果给内层相对定位的div高度height具体值,内层相对层将具有100%的width值,外层绝对层将被撑大。解决方案给内层相对层float属性。

20.ie6下的bug,<head></head>内有<base target=”_blank”/>的情况下,position:relative层下的float层内文字无法选中。这个bug迫使我修公用样式库。

21.终于来了个ff的缺点。width:100%这个东西在ie里用很方便,会向上逐层搜索width值,忽视浮动层的影响,ff下搜索至浮动层结束,如此,只能给中间的所有浮动层加width:100%才行,累啊。opera这点倒学乖了跟了ie。

1、clear清除浮动(添加空div法)

在浮动元素下方添加空div,并给该元素写css样式: {clear:bothheight:0overflow:hidden}12

2、方法:给浮动元素父级设置高度

我们知道了高度塌陷是应为给浮动元素的父级高度是自适应导致的,那么我们给它的设置适当的高度就可以解决这个问题了。

缺点:在浮动元素高度不确定的时候不适用1234

3、方法:以浮制浮(父级同时浮动)

何谓“以浮制浮”呢?就是**让浮动元素的父级也浮动**。

缺点:需要给每个浮动元素父级添加浮动,浮动多了容易出现问题。1234

4、方法:父级设置成inline-block

缺点:父级的margin左右auto失效,无法使用margin: 0 auto居中了12

5、 br 清浮动

<div class="box">

<div class="top"></div>

<br clear="both" />

</div>1234

br 标签自带clear属性,将它设置成both其实和添加空div原理是一样的。

问题:不符合工作中:结构、样式、行为,三者分离的要求。

6、给父级添加overflow:hidden 清浮动方法;

问题:需要配合 宽度 或者 zoom 兼容IE6 IE7;12

overflow: hidden*zoom: 112

7、万能清除法 after伪类 清浮动(现在主流方法,推荐使用)

选择符:after{content:"." clear:both display:block height:0 overflow:hidden visibility:hidden }123456789

同时为了兼容 IE6,7 同样需要配合zoom使用例如:

.clear:after{content:''display:blockclear:bothheight:0overflow:hiddenvisibility:hidden}.clear{zoom:1}