表1-4 CSS属性的浏览器前缀
| 前缀 | 渲染引擎 | 使用该引擎的浏览器 |
| -khtml- | KHTML | Konqueror |
| -ms- | Trident | Internet Explorer |
| -moz- | Mozilla | Firefox、Camino、Flock |
| -o-* | Presto | Opera、Opera Mobile、Opera Mini、Nintendo Wii浏览器 |
| -webkit- | Webkit | Safari、Safari on iOS、Chrome、Android浏览器 |
我们会关注-moz-、-o-和-webkit-前缀,其他的则因为太不常见而没必要在这里讨论。
1. 为什么它们会存在
浏览器使用前缀来尝试一些新属性、值和选择器,即便它们还没有最终定稿--这是一个好的测试方法,在必要时也可以对它们进行修正或者重新定义。如果浏览器一上来就直接使用标准属性,那它们就会被直接锁定在这个特性的实现上而不易变更。
开发者可能会立即使用无前缀的属性,而且也会一直期望它能够保持同样的表现不再变更。如果浏览器在之后对这个属性做了变更,不管是由于它的实现存在缺陷,或者是由于规范本身发生了变更,所有现存的使用了这个属性的网站都有可能面临出问题的风险。除了会出现这样的锁定问题外,这种方式也有可能强迫其他浏览器和W3C去适配它的实现。Eric Meyer在他的"前缀或者弥补"(www.alistapart.com/articles/perfix-or-posthack)这篇精彩的文章中提到了两个真实的例子,讲述了在过去这种不幸的循环是如何发生的。
即使浏览器不会变更它的实现从而不去影响现有的网站,W3C如果改变了它的规范定义怎么办?如果其他的浏览器直接使用更新的规范中的新表现该怎么办?现在你有不同的浏览器用不同的方法来实现同样的标准属性。这不正是当年Netscape 4、Mac IE 5和Windows IE 6并存的那些岁月吗?复杂和不稳定的Hack,充满了缺陷的实现,甚至这些缺陷与实际所需要实现的属性毫无关系,这些问题不断扩散,因为很多特性都与浏览器规范的属性实际不符。
有前缀的属性会告知开发者这个属性只是实验性质的而且很有可能会变更。它给予浏览器在必要时候进行变更的灵活性,这让浏览器能够更快地发布或者重新定义新的属性。这也让开发者能够更快地有机会尝试新的属性,并且能够参与到在真实环境下测试和修订这些特性的过程。
一旦规范开始稳定下来,浏览器也达到了能够正确实现属性的程度,它就可以去除这个前缀。如果开发者在样式表中同样提供了无前缀的属性的定义--这是很好的向后兼容的方式--那么开发的页面就能够自适应最终定义完成的特性。如果开发者没有这样做,那这些页面也不会受到影响--旧有的前缀属性还是能够正常工作。没有任何一个使用前缀的网站会发生问题。
2. 前缀的问题
浏览器前缀实际上还是存在一些问题的。最主要的抱怨是你需要用几行不同的代码来完成实际上相同的一件事,比如:
- div {
- -moz-transform: rotate(45deg);
- -o-transform: rotate(45deg);
- -webkit-transform: rotate(45deg);
- transform: rotate(45deg);
- }
如果使用预处理工具来隐藏前缀属性,那代码的写作者会忘记他们在使用实验性的不稳定的特性,因此,会把他们所使用的特性当作一个已经非常稳定的特性。
尽管使用浏览器前缀的重复代码非常笨重,但根据一个标准属性生成不同浏览器前缀属性的方式所造成的不一致性将会更加恼人。而且随着标准的演进,你就能够移除那些浏览器前缀属性从而让样式表更干净,而不用一直修补样式表,仅仅因为一个无前缀的属性突然变成了非标准属性。让我再次引用一下Eric Meyer的文章吧,他也提到过这种临时前缀究竟会有多"痛苦":
它有点像疫苗--注射的时候是真的会痛没错,但相对于所能够免疫的疾病来说,那真算不了什么,就这个例子而言,你能够对一个坏的情况免疫,不用持续地做预处理优化和浏览器探测。我们只用一次就避免了一个巨大的危害。正确的使用前缀能够避免在一段长的时间后突然爆发的问题。
提示 当你想要从CSS里删除某个前缀时,你可以使用正则表达式来帮你完成这个工作,请参阅www.venturelab.co.uk/devblog/2010/07/vendor-prefixes-what-happens-next。
前缀的另外一个问题是它们不能通过验证。这不是它本身的问题,验证仅仅是为了帮助我们发现问题。因此如果你知道使用前缀时为什么会有错误提示,只要忽略提示就可以了。但是当这种"良性"的错误混杂在普通的错误中时,很难发现真正的错误。
为了解决包括重复代码和不能通过验证在内的这些问题,有些人把前缀属性和正常的样式表放在不同的文件里。通过这种方法,主要的样式表还能继续通过验证(当然样式表中还可能有其他错误)。但是包括我在内的很多CSS开发者并不喜欢这个解决方案。首先,它会增加一个HTTP请求,相对于在样式表中增加几字节而言,一个额外的HTTP请求肯定对性能的影响更大。其次,它会让你忘记还有个额外的样式表,既然它们相对于正常的特性而言更需要关注,那给予它们更多的关注显然很必要。如果一个浏览器改变了它的某个前缀特性的表现,那你就需要马上更新样式表来保持一致。或者当你想简单地了解一下为什么有些特性会表现成某个特定形式的时候,可能会忘记你还使用了陈旧的前缀样式表,而花费很多时间去跟踪问题。因此非常遗憾地说,相对于为前缀属性保留一个独立的样式表文件而言,由于前缀属性而导致样式表不能通过验证的问题严重程度显然要轻很多。
尽管有这些问题,大部分CSS开发者也还都喜欢前缀属性,并且受益于它,正如前面所说,正确使用这些特性就会利大于弊。
3. 使用浏览器特定属性的正确方法
使用浏览器前缀属性的时候,应该总是包含无前缀的属性并将它放在最后。这就可以确保当浏览器可以支持无前缀的属性时,这个属性就能够生效。前缀属性因为位置靠前从而会被靠后的无前缀属性所重载,这才是正确的行为。
举例来说,在Safari 5发布之前,Safari使用-webkit-border-radius属性。它确实曾经很好用,但它的实现从很多方面来看是不正确的(因为W3C重定义了规范以后,它就开始不正确了)。Safari 4及之前的版本不允许单独定义每一个圆角,但是W3C的定义说应该可以这样做。它还使用了错误的语法来定义曲线的弧度。
但这是没问题的。你可以保持-webkit-border-radius属性中包含的错误语法,这对其他非Webkit浏览器其实是不可见的。而在Safari 5中,使用标准属性border-radius以及正确的语法可以实现圆角特性。甚至都无需修改样式表。标准属性已经在那里了,正在等待使用。
虽然我们说应该总是在最后才包含标准属性,不过可能在有些情况下,我认为应把标准属性留空,只使用浏览器特定的属性版本。如果这个属性的规范定义还在不断变更,建议等它更稳定时再把其标准属性包含进去。当规范最终确定,浏览器开始使用标准属性的时候,这个特定属性很可能会被忽略或者淘汰,像这种情况,就没有必要包含这个属性。
一个很好的例子就是CSS3生成的渐变效果。在第2章中,你会了解到这个CSS3的语法还非常新,Firefox和Webkit在浏览器前缀属性中使用了完全不同的语法。这也许会让你决定根本就不使用渐变效果,但从另外一方面来说,它只是纯粹的视觉效果,不会对其他浏览器产生负面影响,也许你只是打算把它用在实验性的网站或个人网站上,比如说iPhone App。即使面临语法变更的风险也想使用渐变特性时,较安全的方法是仅使用前缀版本。当然这样的例子不常见,因为浏览器很少会在语法定义都不太完整稳定时就推出一个特定的前缀版本,即便他们这样做了,你也会继续等待更稳定的版本。
另外一个可选的建议是当你使用某个浏览器前缀版本的时候,最好也加上相关的其他浏览器前缀版本的定义,即使你认为它们现在不会被使用,你也没法否认在将来会有这个可能性。我对此既不赞同也不反对,对我来说,这通常要视情况而定。如果我制作的网站不需要我继续维护,我会考虑加上所有的浏览器前缀属性定义。但如果是一个我会持续维护的网站,我会仅仅加上那些实际需要的浏览器前缀,在以后需要的时候再去添加其他的前缀。这两种方法各有千秋。
不管你选择添加何种前缀属性,在CSS中添加注释指出哪个属性是被哪种浏览器所使用永远是个好主意。它其实没有你想象得那么明显。举例来说,这儿就有一组border-radius属性可能会添加的注释:
- -moz-border-radius: 20px; /* Firefox */
- -webkit-border-radius: 20px; /* Safari 4 and earlier */
- border-radius: 20px; /* Opera, Chrome, Safari 5, IE 9 */
说明 在http://peter.sh/experiments/vendor-prefixed-css-property-overview页面有一个方便的表格罗列了四大渲染引擎的特有属性。通过它可以对比了解哪些浏览器目前使用带前缀的私有属性,哪些不带前缀,而哪些根本不支持相关属性。



