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

script的src和link的href属性值为空的陷阱

2010-8-2 update:经过测试发现,但script的src和link的href属性值为#的时候,也会发生同样的事情:给服务器发送一个HTTP请求,并把当前页面作为返回值。浏览器的异同与下面的结论相同。通过js动态生成的script和link元素也一样。

————-============————-============————

有些时候,我们在写HTML页面的过程中,通过script标签来外联外部的js文件,可是不小心给src属性赋值为空,当然有时候也会在link元素中带上了空属性值的href属性,我们想当然的认为这个没多大的影响,浏览器也会按照你认为的方式来解析它们。但是这个想当然估计错误了。先看几个测试页面:《HTML5 Doctype下href=”"》,《HTML5 Doctype下src=”"》,《XHTML1.0 Doctype下href=”"》。

使用firebug来查看DOM节点树的link和script节点,你就会发现怪异的事情发生了:link和script节点的内容居然是当前html页面的内容!!确实不可思议,就连容错机制最好的XHTML1.0的DTD声明也无济于事,这恐怕就是浏览器渲染页面的问题了。script节点src属性为空的时候就更恐怖了,会造成了语法错误。可更不可思议的是,出现这个问题的浏览器居然不是IE。经过多个浏览器的测试,IE和Opera都能很好的处理这个问题,可是Firefox、Chrome、Safari居然出现了

对于这个初步总结为:当link标签的href属性为空、script标签的src属性为空的时候,浏览器渲染的时候会把当前页面的URL作为它们的属性值,从而把页面的内容加载进来作为它们的值。动态修改它们的href或者src属性值为空的时候,也将会出现这个问题:《动态修改href为空》。

因此,在我们平时写HTML页面,或者动态生成这两个标签的时候,尽量避免陷入了上面出现的陷阱,好好对待浏览器,编写符合规范的HTML标签语法和嵌套规则,多余的就别多此一举去倒弄,它回报给你的将是更少的开发时间和精力。

当某天你测试上面给出的测试样例并没有发现这个问题的时候,那就是浏览器修复了这个bug了。

@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等工具查看页面源码。

页面中meta的charset声明将如何影响页面加载性能

关于页面charset会影响浏览器加载和渲染性能的了解,以前一直都没怎么关注,今天在Google code上了解到了这一点:在HTML文档的头部使用meta标签显式声明页面的charset将会使得浏览器更快的加载、渲染页面和执行页面内的scripts等等组件

现在先来了解一下浏览器是如何接收HTML文档的数据和进行重新编码的:从服务器传输到浏览器的HTML文档内容是以一系列的字节来传输的,并且连同字符编码信息一起传输。在HTTP响应头中详细指定字符编码信息,或者在HTML文档的meta标签中明确的指定,浏览器就会使用这个指定好的字符编码信息去转换从服务器端传输过来的字节流,并且渲染到浏览器窗口中显示出来。如果浏览器不知道这个字符编码信息,就不会正确的渲染页面,大多数浏览器在执行任何的Javascript和渲染页面之前会缓冲了一定数量的字节,同时浏览器还会去查找字符编码信息来处理这些缓冲的字节。

如果没有明确的指定字符编码信息,不同的浏览器对缓冲的字节数和默认的字符编码信息都是不同的。因此,一旦他们缓存了许多必须的字节而又开始渲染页面,之后浏览器查找到了页面指定的字符编码信息跟浏览器默认的不同的时候,浏览器就会按照在页面中查询到的字符编码信息重新渲染页面,有时甚至会重新请求页面内的资源。

因此,为了避免上面将会出现的延时或者不必要的操作,就需要在HTML页面中明确的指定charset的类型,并且在head中是越前越好(比如直接在head标签之后,这一点主要是针对Firefox3.5,Firefox3.5如果在页面的前2048 bytes中没有查找到字符编码信息,就会使用默认的字符编码信息来编码字节和渲染页面)。因此,Google code对于charset提出了四点建议:

  1. Prefer HTTP over meta tag parameters。意思大概是在HTTP响应头中和在meta标签中同时声明charset信息。比如: <meta http-equiv=”Content-Type” content=”text/html;charset=utf-8″>
  2. 尽早声明charset,“Near the top”。推荐是直接在head标签之后就声明。
  3. 始终指定的内容类型。在浏览器开始查找字符编码信息之前,它必须首先要确定被处理的文档内容的类型。如果这个没有在HTTP头或者HTML的meta标签里指定,浏览器就会使用算法尝试去“猜测”这个类型。这个处理操作就会造成额外的延迟。所以,为了双方面的性能考虑,需要给所有的资源都总是指定内容类型,而不仅仅是文档(比如HTML文档的类型是text/html)。
  4. 确定使用了正确的字符编码。在HTTP头和Meta标签中声明的charset信息一定要相同,这点非常重要。否则会使得浏览器不能正确的渲染页面或者在渲染页面之前造成额外的延迟。

对于上面Google code提出的四点建议,我想再提一点的就是:对于Javascript、css等等在这些资源的响应头中最好也声明和HTML页面相同的字符编码信息,来统一浏览器处理字节编码。

对于没有明确指定charset信息的HTML页面,浏览器在使用默认的charset来渲染页面之前,会缓冲一定数量的字节数,这篇文章中对这个缓冲字节数做了更详细的测试:《Performance Implications of “charset”》。还有更多关于charset的优化信息,可以参考:《Browser Performance Issues with Charsets》,《Specify a character set early》。

更多关于优化的叙述,可以浏览:《优化试试玩》,《尝试Gzip压缩

《高性能网站建设进阶指南》,终于出来了

期待已久的《高性能网站建设进阶指南》终于出来了。作为口碑前端的一员,都感觉灰常Happy。

相信大家都熟悉那本《高性能网站建设指南》,而作为姊妹篇的《高性能网站建设进阶指南》也绝不会逊色于前者。通过校验该书的机会,也学习到了非常多关于web前端优化的具体的最佳实践,包括图片优化、Load Javascript without block,编写高效的Javascript、css代码,后加载(Initial Payload),拆分window.onload代码等等最佳实践,还介绍了众多的前端开发工具。可以总的来说:《高性能网站建设指南》是一本理论的书,而《高性能网站建设进阶指南》则是实践篇。

它确实是一本值得去反复阅读的好书!!