无聊哥的代码 — “鸡”查询

刚上班,首先是文档哥,之后就是无聊哥了,真杯具。但是杯具下就好了,人不杯具。消遣的最好方式就是练练脑,写代码,query就是杯具下的产物。先出土个雏形,有时间还成无聊哥了再来扩展下。

query根据CSS规则来查询DOM,提供了基础的CSS选择符:

tag,.class,#id,[attr],tag[attr],tag[attr="value"]...

在测试跟jquery的查询速度比较的时候,再添加了对querySelectorAll(语法是:baseElement.querySelectorAll(selector),这里的baseElement其实是可以为除了文本节点和空节点之外的任何的DOM节点的,别以为只是document而已)的支持。编写的代码中加入了本人认为不错的编写方式,如果你对阅读代码有兴趣,那就看看吧,欢迎提出更好的方式(如果这里显得难看了,就自己下载来看:query.js):
(全文…)

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 DOM Element加载js文件的延伸

Script DOM Element动态加载js可以使得js文件跟其他资源并行加载并异步执行,这是我们都知道的事实。但是有一点,或许有些人没有想到,js脚本在何时才会开始加载?

话说多了不足为信,看一个简单的测试:《测试js何时开始加载(用firebug查看HTTP瀑布图,或者HTTPWatch、Inspection等等均可)》,代码示例如下

var s = document.createElement("script");
s.setAttribute("src","test.js");

var l =document.createElement("link");
l.setAttribute("href","/wp-content/c/style.css");
l.setAttribute("rel","stylesheet");

var img = new Image();
img.setAttribute("src","/wp-content/uploads/2010/06/performance.png");
//document.body.appendChild(img);
document.body.appendChild(l);
document.body.insertBefore(s,document.body.lastChild);

通过上面的测试发现:图片先加载,之后是js,最后是css文件。但是看上面的代码,图片并没有插入到文档中,但是它还是加载了;虽然js先于图片声明,但是却在图片之后加载;CSS虽然后于js声明,但是先于js加载。

这就有意思了,通过上面的测试说明:使用new Image声明的图片,在设置它的src属性之后就会开始加载;js和css文件在插入到文档之后才开始加载。通过这个特点,我们在性能优化方面就可以再总结几点想法了:

  1. 通过使用Script DOM Element的方式加载js的时候,虽然说是可以同其他资源并行加载和异步执行,但是如果加载的js可以在页面加载完成之后才需要起作用的,可以在</body>前面来声明并插入到页面中,从而可以多让其他资源并行加载,而js最后加载并执行,加快页面的加载速度,这样的话将js插入到head和document.body都一样的效果。
  2. 同样使用Link DOM Element的方式加载css文件的时候(如果一定要这样做的话),因为样式表关系到整个页面的布局和样式渲染,所以它需要尽早的被加载进来并开始渲染,以免造成页面内容显示的延迟和IE下“白屏”的问题。
  3. 在优化图片加载的需求上,虽然图片加载是并行的,但是浏览器并行加载资源的数目是受到限制的,对于那些不是在浏览器第一屏(打开网页时在浏览器视窗内看到的内容)看到的图片、图片Tab组件、图片列表等等,可是使用上面的方式动态来插入,在页面其他重要的内容和资源都加载完毕的时候,其次的图片动态的插入,使得浏览器开始加载这些的图片。对于加快页面的显示速度,这也是不错的方式。

浏览器解析样式表的异同

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