CSS2.1的容错机制

CSS2.1是当前普遍使用着的CSS版本,平时如果循规蹈矩的编写CSS,或许不会发现问题,可问题就是:如果想要保存的时候,不小心按Ctrl+s的时候多留了一个s在CSS文件里,问题就开始来鸟;或者是不小心在规则的大括号外边多写了一个分号……出错的事情是千奇百怪的,对于新手来说频率可能会高些。出现这样的问题的时候、调试起来你可能会感觉很莫名其妙,这就需要了解一下CSS2.1版本的容错机制了。

CSS2.1的容错方式总的来说就是:对于出现一些无效的属性、属性值、@-keywords等等,它就会忽视这些样式声明或者整个规则声明。对于无效的属性、属性名会忽视该样式声明,对于@-keywords中keywords无效时,会将整个@-keywords包含的样式声明都忽略掉。

既然CSS2.1有这个容错机制,那么还有其他一些平时比较容易忽略的错误导致的样式失效的问题,下面举例出一些实例,看完下面的实例,基本可以了解CSS解析器解析CSS代码的一些原理了。

  1. 不要在CSS每个规则之间插入除空格之外的无效字符或者插入不合CSS语法的注释,否则该无效字符之后的第一个规则无效,例如:
    body{color:green;}.  /*这里不小心多了个点号就杯具了*/
    p.ten{color:red; background-color:green;} /*这个规则是无效的*/
    p.one{color:red;} /*这个有效*/
    

    有一种情况比较特殊,当无效字符等使用大括号“{}”包含的时候,它下面的规则正常应用了,因为浏览器CSS解析器将它当作一条规则来处理。比如:

    body{color:green;}
    {.}
    p.ten{color:red; background-color:green;} /*它可以work啦*/
    
  2. 跟上面对应的就是在样式中出现无效字符或者插入不合CSS语法的注释,则当前无效字符之后的第一条样式无效,例如:
    p.one{border-style:solid; . color:#00c; font-weight:bold;}  /*color无效*/
    

    但是有一种情况比较特殊,就是存在分号“;”的时候,前面带有无效字符也没影响。可能浏览器CSS解析器是将样式规则用分号进行拆分成数组,每个分号之前的样式不会影响下一个样式,比如:

    p.one{border-style:solid; . ; color:#00c; font-weight:bold;} /*color有效*/
    

    因此从这里,有可以引申出一个问题:当编写每一个样式的时候最后没有加上分号,当前样式的下一个样式就会无效,因为浏览器把它解析成一整个属性:属性值对了(如果只有一个样式,或者是最后那个样式没有加上分号,则没什么影响。根据数组的split原理,建议是最后那个样式不加分号,这样就减少一个空的数组元素),比如:

    /*下面就杯具了*/
    p.one{
      border-style:solid
      font-weight:bold
    }
    /*这还不错*/
    p.one{
      border-style:solid;
      font-weight:bold
    }
    
  3. 不要在颜色值(英文、十六进制等等)上加上双引号,否则该样式规则无效,例如:
    body{color:"red";background-color:"green";}
    
  4. 给元素设置border的时候,如果没有设置border-color,则会使用字体的颜色来代替;如果没有设置border-width,则默认是medium。例如:
    p.one{border-style:solid; color:#00c;}
    
  5. 在一些连写的CSS规则中,如果其中一个元素的规则声明出现错误,则整个规则无效,比如:
    p.one,em @,strong{border-style:solid; color:#00c;} /*这里em @ 出错,造成整个规则失效*/
    
  6. 如果在一个规则中样式进行了重复定义,如果其中一个样式的值无效(或者为空),那么不会覆盖有效的值的样式,因为浏览器会忽略掉无效值的样式声明,比如:

    p.ten{color:red; background-color:green;color:ss;} /*color还是red*/
    
  7. 规则声明不可以嵌套:p.ten{color:#00c;p.ten{color:#f00};background:#0c0}
  8. 在规则的特殊性方面,对元素声明样式时,将样式定义在它自身上和定义在它父元素上的优先级差别:将样式定义在子元素上,即使是使用标签类型的规则都比父元素使用ID、class、important等这样高优先级的都高,比如:span是p元素的子元素,p带有ID属性为ten
    span{color:red;} /*文本的color样式还是red*/
    body p#ten{color:green !important;}
    
  9. 对于无效的属性或者属性值,CSS解析器会直接忽略掉这些属性或者属性值,也就使得这条样式声明无效,比如:
    img { float: left }       /* correct CSS 2.1 */
    img { float: left here }  /* "here" is not a value of 'float' */
    img { background: "red" } /* keywords cannot be quoted */
    img { border-width: 3 }   /* a unit must be specified for length values */
    
    /*解析过后,是:*/
    img { float: left }
    img { }
    img { }
    img { }
    
  10. 属性值中双引号和单引号要闭合,否则结果很悲剧,例如:
    p {
       color: green;
       font-family: 'Courier New Times
       color: red;
       color: green;
    }
    解析过后,就成了:
    p{
      color:green;
      color:green;
    }
    

平时是我们太循规蹈矩的去编写CSS代码了,对于一些因为粗心造成的失误而导致样式失效的问题,浏览器又没有很好的提示CSS错误的功能,所以只能是通过肉眼看样式是否生效来测试CSS代码了。其实当你不遵循CSS语法来写的时候,会发现更多的CSS的解析失效问题,或许这就是CSS Hack被发现的源头吧……

详细CSS2.1容错机制可以浏览:http://www.w3.org/TR/CSS21/syndata.html#parsing-errors

浏览器解析样式表的异同

2010-6-28 updates

之前的测试都在页面中直接通过link元素外联css文件,但是动态插入的情况呢?(非常感谢Emu的提醒)

因此,又测试了使用DOM Element的方式来动态插入link元素,并外联css文件,可是问题依旧,《测试示例》。IE、Chrome、Safari照常需要等到全部的样式表都加载下来才开始呈现页面样式;FF、Opera是加载完一个样式表就渲染页面,而不需要等到全部的样式表都加载下来。

之后,测试使用一个定时器(setTimeout)的方式来异步执行动态加载css文件,《测试页面》,这下子就有点意思了:IE出现了无样式内容闪烁,仍然需要等全部的样式表都加载下来之后才呈现页面;Chrome、Safari跟FF、Opera一样了,加载完一个样式表就渲染页面,不需要等到全部的样式表都加载下来。

不过定时器的方式虽然解决了Chrome、Safari的问题,但是使得IE可能会出现无样式内容闪烁,所以也并不是很完美的方案,就看网站的用户群偏向哪些浏览器多一些了,并对这些浏览器做这方面的优化。

——————— 完美的分界线 ———————-

一直存在一个疑问:IE同其他主流浏览器的兼容性差异都涉及到布局、HTML、Javascript、CSS规则等方面,但是对于解析样式表来渲染整个页面呢,是否也存在异同?当看到Steve Souders的博文《Frontend SPOF》,迷惑揭开了,文中对各种浏览器解析样式表的机制做了如下描述:

Browsers are split on how they handle stylesheets. Firefox and Opera charge ahead and render the page, and then flash the user if elements have to be redrawn because their styling changed. Chrome, Internet Explorer, and Safari delay rendering the page until the stylesheets have arrived. (Generally they only delay rendering elements below the stylesheet, but in some cases IE will delay rendering everything in the page.) If rendering is blocked and the stylesheet takes a long time to download, or times out, the user is left staring at a blank page. There’s not a lot of advice on loading stylesheets without blocking page rendering, primarily because it would introduce the flash of unstyled content.

上面说的很明白,对于样式表的加载和渲染的原理,不同的浏览器下有不同的行为,主要是分为Firefox、Opera和IE、Chrome、Safari两类。

Firefox、Opere在加载样式表的时候是边加载边渲染。这样既有利也有弊:利在使得页面可以尽快的开始渲染,而无须等待全部的样式表都加载下来之后再开始渲染,延迟了页面渲染;而弊端在于之前加载并渲染的样式在后面又重新定义或者修改了布局样式,那么将会造成一定程度上的闪烁,闪烁的程度主要看所影响的元素的范围和数目。

而IE、Chrome、Safari则是在全部的样式表完全加载下来之后才开始渲染页面样式。这样做也同样是有利有弊:利在于可以避免Firefox、Opera中出现的闪烁的问题(虽然在IE下闪烁是经常的事情),可以确保样式会统一解析并渲染页面;而弊端在于页面全部样式表的加载延迟了页面渲染的时间,如果样式表加载的时间较长,或者加载错误,将会导致页面一直处于空白状态或者无样式状态,还有可能是“无样式内容的闪烁“的问题的出现。

为了证实上面的说法,进行了一个测试,这个测试的关键是延迟样式表的加载,但是不能延迟了HTML页面的加载和解析,并在页面中添加几个样式表做比较。为此,利用PHP的sleep函数在其中一个样式表中延迟3秒,并放到body的底部来加载,而另外一个样式表放到head里加载:《浏览器解析样式表的测试》。测试结果很明显:FF、Opera是首先解析了第一个样式表,等第二个延迟加载的样式表加载下来的时候,改变了第一个样式表的样式,出现了闪烁;IE、Chrome、Safari则是需要等到第二个样式表也加载下来的时候才开始渲染页面样式,导致了3秒的渲染延迟。

通过上面的测试证实,明白了在页面中Minify CSS、Put CSS at the head的实质理由和重要性:最小化页面渲染延迟时间。并总结出下面几点:

  1. 尽量避免将样式表放置到body中来加载,避免IE、Chrome、Safari延迟页面开始渲染的时间,也避免IE下的“空白闪烁”和“无样式内容的闪烁”等问题。
  2. 如果页面中有几个样式表,尽量合并为一个样式表文件,避免FF、Opera样式闪烁的问题。既然文件大小总量不变,那就尽量保持HTTP请求数的最小化吧。
  3. 在样式表Minify后,再Gzip压缩下,效果更佳,最小化各个浏览器对于加载并解析样式表的差异性而造成不同程度的用户体验的影响。
  4. 尽早的让样式表加载,最好是在加载并解析HTML之后就开始加载样式表。但是也要避免样式表后紧跟行内脚本,从而造成对后续资源的阻塞。
  5. 对于当前页面使用不到的样式表,就删除掉,避免整个站点的页面都使用同一个样式表,从而节省不必要的宽带和使得页面的渲染时间越早越好。

@font-face的性能测试

对@font-face的作用,也是今晚在看这篇博文《@font-face and performance》的时候,才知道使用它可以自定义显示字体。我想在浏览器中加入@font-face的功能是处于一定的情况的,比如使用特殊的字体来显示页面某部分的字体,从而避免采用图片的方式来显示特定字体。出发点是很好的,但是……

解析@font-face的机制在不同的浏览器下是不同的。

对于@font-face更多的叙述,在上面提到的那篇博文中有了详细的描述。但是对于它提出的说“IE doesn’t render anything in the page until the font file is done downloading if there is a SCRIPT tag above the @font-face declaration.”,说的是当在声明@font-face的前面如果存在script标签的时候,IE下不会渲染任何东西,页面一片空白,直到@font-face指定的字体加载完为止,这点经过测试,确实如此。

在声明@font-face的标签之前没有script标签的时候IE下并没有出现阻塞页面渲染的情况,但是如果前面有script标签的时候,不管是否是紧跟这script标签,只要在@font-face标签前面有script标签,就阻塞页面渲染:《没有script标签》,《紧跟script标签》,《不紧跟script标签》。

同时,我测试了@font-face是否会阻塞页面其他资源加载,测试发现并没有这个问题:《是否阻塞资源加载的测试》,而且在字体加载下来之前,忙指示器会一直处于等待状态。下面是各种浏览器对@font-face的测试比较结果,结果分为三类:IE,Firefox、Opera,Safari、Chrome。

  1. 在Firefox、Opera中,并不会阻塞页面其他资源的加载,也不会影响页面渲染,而且在字体加载下来之前,对想应用该字体的内容会使用默认的字体显示,直到字体加载下来之后再渲染为指定的字体。因此,这两个浏览器测试效果最佳。
  2. 在Safari、Chrome中,并不会阻塞页面其他资源加载,也不会影响页面渲染,但是在字体加载下来之前,对应用该字体的内容将会首先空白显示,但是内容尺寸是存在的,直到字体加载完成,才渲染字体样式。
  3. 在IE下就分不同的情况了,如上所述,前面是否有script标签的情况。

对于@font-face的杯具的性能问题,如果没有必要,就尽量避免使用。如果一定要使用的话,可以使用使用lazy load的方式来加载,例如博文中给出的例子:

function lazyload() {
  var sRule1 =
	"@font-face {" +
	"  font-family: 'Yanone';" +
	"  src: url('/bin/resource.cgi?type=font&sleep=6');" +
	"  src: local('Yanone'), " +
        "url('/bin/resource.cgi?type=font&sleep=6') " +
        "format('truetype');" +
        "}";

  var style1 = document.styleSheets[0];
  if ( "function" === typeof(style1.insertRule) ) {
        // Firefox, Safari, Chrome
	style1.insertRule(sRule1, 0);
  }else if ( "string" === typeof(style1.cssText) ) {
	// IE
	style1.cssText = sRule1;
  }
}

将上面的lazyload函数添加到页面的onload事件中去执行,这样就保证了页面的正常渲染并且实现特定字体样式的功能:《lazy load测试》。这个主要是解决IE下@font-face前面有script标签的问题,在其他浏览器下显示的时候,也都还是会在应用该字体的内容中产生空白闪烁、或者是默认字体和指定字体之间的切换闪烁。也都还是在一定程度上影响了用户体验,感觉有些不爽。“Tradeoff is anywhere!!

上面的三个测试都是使用内联CSS的方式,考虑到使用外联CSS样式表的情况,又对此做了一番测试,发现了不同的状况:

  1. 当link放在head时,IE6、8(没有IE7,测试不了)都是死心眼,偏执要阻塞整个页面的加载,在字体下载下来之前整个页面一片空白,其他浏览器跟内联CSS一样的显示:《外联CSS测试(在head内)》。
  2. 当link放到body中时,IE8还是阻塞整个页面的渲染,可是IE6就不同了,IE6会阻塞整个页面样式的渲染,但是不会阻塞link前面的内容的显示,而是阻塞了其后面的内容的显示了,直到字体加载下来之后才开始渲染页面样式和显示link后面的内容。其他浏览器跟内联CSS一样的显示:
    外联CSS测试(在body最底部)》,《外联CSS测试(在body中部)
  3. 当link前面带有script标签的时候,因为外联在head头部的时候在IE各版本都阻塞了,所以这个条件link在页面的任何位置都无所谓,主要测试link前面带有script标签,那就把link放在body中来测试。这下IE6也杯具了,阻塞了整个页面的渲染,IE8照常阻止整个页面渲染,其他浏览器同上。《外联CSS测试(在body中部),前跟script标签》,将link放到body最底部也一样阻塞整个页面的渲染:《外联CSS测试(在body最底部),前跟script标签》,其他浏览器同上。

对于页面中同时有script和link、style标签是很正常的事情,但是当样式中有@font-face的时候,情况就复杂了。更多的测试条件大家可以自行测试。有错误的地方,请不吝留言交流。

重构一个简单的导航

昨晚在浏览Delicious网站的时候,又看到了这种样式的导航样式:

之前一直很想做这样的一个导航,可是苦思都不知道良方,还一直认为这需要几个背景图片,着实复杂;还有hover效果,更是会难些,而且有兼容性问题。之后在FF和IE6下测试了下Delicious的兼容性效果,它在ie6下的样式,使用了普通的背景样式,没有上面所看到的折角效果。

之后看了下它的背景图片,一下子豁然开朗了,关键在于使用负margin来实现即可。下面是我实现的样式截图:

而且在兼容性和代码方面都有所改善。在IE6下也可以实现折角的效果,但是没有hover效果。而在Delicious里是在超链接a标签下嵌套了span标签,在a:hover伪类下来处理span的背景变换。而我直接在li标签中使用:hover来处理鼠标hover的背景切换,代码更加简洁了点,并且通过hack来解决ie6下兼容性问题(Updates:因为把:hover写在了li标签上,所以可以直接去掉hack来兼容ie6的css样式,也能做到稳步退化的效果)。

具体的CSS样式和背景图片可以浏览《折角导航栏效果》页面,使用firebug等工具查看页面源码。

CSS中两种编写规则的测试

对于在css中声明元素的背景(background-image)的时候,或许下面三个问题大家没有多大关注到的,不如下面的一条规则:

.bgimage { background-image: url("/images/button1.gif"); }
  1. 想象一下,当页面中没有元素使用了这个class的时候,背景图片是否会被下载?
  2. 当页面中有元素使用了它,但是该元素又设置了display:none和visibility:hidden的时候,图片是否会被下载?
  3. 当规则出现覆盖的时候,比如在接下来一个外联的css文件中重新定义了bgimage类,并且使用了不同的背景图片的时候,前面那张背景图片是否会被下载?比如:

    .bgimage { background-image: url("/images/button2.gif"); }
    

对于上面的三个问题,Steve Souders已经在他的博文《5e speculative background images》里做了详细的叙述,在这里也先做一个原文意思的翻译:

  1. 对于第一种情况,没有元素应用bgimage类的时候,图片不会被下载,这在所有浏览器都是一致的。
  2. 当元素同时定义了display:none和visibility:hidden之后,不同的浏览器出现了不同的现象,Firefox3.6和Opera10.10不会下载背景图片,而IE8,Chrome,Safari却下载了,他还提供了一个测试页面《hidden background images》。

    但是,经过我的测试发现,全部的浏览器对元素设置了visibility:hidden时,也是会下载背景图片(为此还跟番茄来打赌,结果输了,只能承认是我人品有问题了,-_-)。在此提供两个测试页面,供大家自行测试:《display》,《visibility》。

  3. 对于第三个问题,Steve Souders解释说也是“depends on the browser”,不同浏览器有不同的现象。Chrome和Safari下载了两个背景图片,IE、Firefox、Opera只下载了第二个图片button2.gif。《测试页面

上面对于CSS中对背景图片的加载问题的三种情况的测试,是比较有意思滴。可以根据这几种情况来提高编写CSS的质量,避免不必要的背景图片在Chrome,Safari,IE下被加载了,造成了性能问题,尤其是第三种情况。但是,根据Steve Souders的一番调查,用他的博文上说:

I went on a search and couldn’t find any popular web site that overwrote the background-image style. Not one. I’m not saying pages like this don’t exist, I’m just saying it’s very atypical.

出现这种情况是比较少见的,但肯定还是会有。

说了那么多,上面的叙述只是个引子,接下来才是题目中说的两种编写规则的测试,但是这个测试也是从上面的几种情况中想到的。为此,我有一个疑问:当CSS代码中很多标签都引用了同一个背景图片,如果把这些标签都独立起来写规则(比如下面代码所示),页面是否会跟服务器进行了相应多的次数交互呢?

<!-- 是否会跟服务器进行了三次的交互呢?即使第一个后面的请求都是从缓存中读取的,但是也必须会跟服务器交互一次来确认吧? -->
#test1{background:url(test.gif) no-repeat;}
#test2{background:url(test.gif) no-repeat;}
#test3{background:url(test.gif) no-repeat;}
...

假设我的疑问是正确的,那么让我们来进行下面的测试:

第一种、把所有都使用同一个背景图片的标签都定义在一起,这样就只有一个CSS规则:

#test1,#test2,#test3...{
  background:url(test.gif) no-repeat;
}

第二种、把所有都使用同一个背景图片的标签根据具体情况都分开定义:

#test1{background:url(test.gif) no-repeat;}
#test2{background:url(test.gif) no-repeat;}
#test3{background:url(test.gif) no-repeat;}
...

先不从表面来说这两种写法的孰好孰坏,下面提供了两个测试页面,测试条件是:

  1. 在页面中有2000个div元素,分别有不同的id属性
  2. 为了尽量保持页面的大小,CSS规则都使用外联link的形式引入
  3. 因为这两个测试主要是为了测试两种方式的渲染速度,所以尽量保持了两个CSS文件都是大小相同的(86多K)
  4. 使用相同的背景图片,但是文件名不同,以防造成在测试的时候受缓存的影响。

基本条件都差不多了,现在来测试吧:《拆分的方式》,《结合的方式》。测试结果我就不说了,大家可以自行测试,毕竟相信浏览器,相信自己的眼睛和判断。

其实对于上面使用结合的方式来归类一些具有相同CSS规则的写法,是我之前在口碑实习期间,跟师傅乌龙茶学来的,刚开始还以为会增加一连串的CSS规则,给CSS文件页面的大小增加了不少的字节,但是当你整合好整体的CSS样式之后,你会发现,其实页面会有一个很好的架构,维护起来非常的方便(关于维护这点,有一个经验让我印象非常深刻,就是在今年4月20号的时候,公司要求口碑整站首页要变灰,当开始知道这事的时候,纳闷起来了,那么多首页,在一个下午的时间都要变灰,包括背景、链接、文字。但是,当我在修改灰色版本首页的时候,因为师傅之前是使用了结合的方式,背景、链接等等颜色样式都整合在了一起,所以整个CSS文件,只需要改动几处,问题就解决了,Fuck,还真省事)。所以,把具有相同CSS规则的标签的样式都写在一起,一个维护上也都是非常方便的。

今天去口碑网逛的时候,看到自己最后切的二级城市的页面改版的项目上线了。欣慰啊~,所以共享下本人在编写页面期间写的CSS文件,供指正:《http://www.ilovejs.net//lab/default-css/koubei.html

HTML元素的默认样式

在surfing过程中,在淘宝UED的《Reset CSS研究(八卦篇)》中得知了如何查阅给予gecko内核的浏览器的HTML默认样式的清单的方式(只需要在gecko内核的浏览器中的地址栏中输入:resource://gre/res/html.css即可),下面提供了我在Firefox3.6.4版本中获得的HTML.css清单。将它代码高亮,有助于阅读,同时由于篇幅问题,对于webkit内核的浏览器、IE6的HTML默认样式,仅提供一个url链接:《webkit内核的HTML.css(Chrome,Safari)》,《gecko内核的HTML.css(Firefox)》,《ie6的默认样式》,《HTML4默认样式》,《CSS1默认样式》,《IE6、7、8默认样式对比列表》,《CSS2.1 User Agent Style Sheet Defaults》,《Mozilla quirk.css》,《Symbian default CSS》。

各种浏览器之间对HTML元素默认的样式的不统一性,正是YUI reset cssHTML5 Reset Stylesheet以及Eric Meyer’s CSS reset被提出来的必要性。

更多参考:《踏上寻找webkit内核渲染HTML的默认样式之路》,《HTML默认样式和浏览器默认样式》,《Reset CSS研究(八卦篇)》,《Really Undoing html.css》,《SUNThink》,《CSS Compatibility and Internet Explorer》,《Cascading Style Sheet Compatibility in Internet Explorer 7》,《HTML5 Boilerplates》,《Eric Meyer’s CSS reset》,《YUI reset css》,《Mozilla all default Stylesheets》,《Webkit WebCore