`
hax
  • 浏览: 952737 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

面向未来的CSS实践

阅读更多
本文源于http://ued.taobao.com/blog/2007/08/12/css-notes/的讨论。淘宝UED团队的小马对taobao的CSS编程原则描述如下:
小马 写道
* 尽量不使用hack
* 尽量不使用ie6不支持的选择符
能符合这两个条件的最简洁的写法,就是我们的目标。


由此展开,我论述了在CSS实践上的另一种思路。这是我自去年年中至今年4月在SNDA进行商城开发过程中对于前端web设计编程的思考和实践的首次书面整理。

如下:

对于taobao网站css的原则,我个人认为这两条原则是较为保守的,当然对于taobao这样的网站,采用比较保守的策略是很合情合理的。

我谈一下我对着两个原则的一般看法。

对于hack,我觉得要区别对待。对于使用selector或利用其他特定浏览器的bug来做hack的,需要谨慎。因为这类hack没有向后兼容性,很可能碰到下个版本的浏览器,支持了原先不支持的selector,或者修复了原先的bug,这就惨了。MSIE7就是一个典型例子。实际上90%的hack都是为IE准备的,而对于IE来说,最好用condition comments,这是IE团队推荐的方法 —— 它的优点除了向后兼容性的保证之外,还有就是可以把IE特定的代码写在单独的stylesheet里(其他浏览器可以不load它从而节约带宽),但是缺点也是这个,就是同一个效果,要在两个样式表里维护。

对于第二条原则,即不使用IE6不支持的selector,我觉得对多数网站来说,就过于保守。

我推崇一种面向未来的CSS实践。即大胆采用CSS2.1甚至部分CSS3的特性。因为绝大多数特性,Firefox、Opera、Safari等都已经很好的支持了。MSIE7也改进了许多,将来IE也无疑终究会完全支持CSS2.1。对于目前的IE,除了graceful degradation的方式(实际上整个内容样式分离的原则和良好的CSS设计可以确保这点,比如淘宝以前的“裸体”所体现的),可以考虑通过特定手段来patch之。

在这点上,我必须说,我原来也是一直坚持只用ie6的selector的。是什么改变了我?就是Dean Edwards的IE7!它的出现不仅在于实践价值——即提供了一个对于IE的补丁,让开发者可以直接写CSS2甚至CSS3。对我来说,它更是观念上的革新,原来事情可以这样做!

所以,尽管DE的IE7在大型商业网站上还是存在一些问题的(主要是Ajax下的样式刷新带来的性能问题),但是它启发了我们可以从另一个角度来思考CSS的运用。

比如说,从实践的角度出发,所有IE6不支持的各种CSS selector中,最不可缺少的是什么?

由我个人的经验来看,最有用的就是多class。其次是一些伪类。

一个最常见的例子是,当button获得focus的时候,我们希望改变它的样式。

CSS2.1下可以:
input[type=button] {…}
input[type=button]:focus {…}

对于IE,我们可以给input加一个class来表明它是button,我们也可以通过脚本来给当前focus的元素增加一个pc-focus类(pc前缀表示伪类)。但是同时有button和focus怎么办涅?IE不支持多class,意味着,你不能这样写:

input.button {…}
input.button.pc-focus {…}

IE会把上述代码错误的解释为:

input.button {…}
input.pc-focus {…}

结果你为button准备的focus效果可能会跑到其他input上,例如radiobox、checkbox上。

所以通常会看到有人会给出两个class一个是button,一个是button_focus,onfocus的时候,把button替换成button_focus。

input.button {…}
input.button_focus {…}

当然它可以工作,对于一向只用一个class的人来说,甚至可以用不太严谨的方式,在onfocus的时候className += '_focus',在onblur的时候className.replace('_focus', ''),这段代码可以通用,而不必为button写一遍,又为radio或者checkbox再写一遍。但是总的来说,这恐怕不是一个好方法。例如button_focus不能复用button的样式特性。如果你要复用,必须写成:

input.button, input.button_focus {…}
input.button_focus {…}

每一处类似的情况都要记住这个写法(且两句顺序不能颠倒)。
特别讨厌的是,即使在另一个地方,你不想要focus效果,因此只需要input.button,而不需要input.button_focus,但你也要记得第一句的写法,否则一旦有了焦点,input.button样式就失效了!

那么我们可以考虑另一种方式,即不是把button替换成button_focus,而是两个并存,onfocus的时候addClass('button_focus'),onblur的时候removeClass('button_focus')。写CSS的时候要注意优先级一致和顺序问题:必须保持.button_focus的样式声明在.button之后。

对于input的其他情况,也照例:

input.radio {…}
input.radio_focus {…}

onfocus的时候addClass('radio_focus'),onblur的时候removeClass('radio_focus')。

一般来说,在IE里我们就到此为之了。注意,对于一个元素的class属性里包含更多class例如3个或者4个class的情况,要用这种方式是非常麻烦的。因此遵循小马所说原则的开发者会尽量避免使用多class。

多个class能够让我们以正交的方式处理问题,而避免多class,实际上是强迫我们尽量把问题平面化,降低了我们对于设计的表达能力。而在实际需求的逼迫下,开发者往往会不得不作出一些作为特例的代码(例如上面的button到button_focus的替换法)。在团队开发中,假如团队缺乏一些处理这类问题的通用“模式”的话,结果会更麻烦。

总之,避免多个class的selector,就是一种典型的实现工具对设计方法的不合理约束,对于设计的简单性、可维护性、可复用性都可能造成伤害。

我们能否换一种思路思考呢?

我们不是削足适履,仅仅在没办法的时候才用一些特别代码来达到本质上可以用多个class的selector来表达的效果。而是确认,多个class的selector是我们的基本需求。问题就变成了,怎样让IE也支持多个class。

让我们回顾在前面的若干个focus/blur事件处理函数,本质是相同的。我们能否避免写那么多本质相同但可能很复杂的focus/blur处理函数?这是可行的,例如对于一个class属性包含若干个class的情况,你可以给所有的class X都add一份对应的X_focus。比方说,A[class='x y']如果获得focus,那就可以改为A[class='x y x_focus y_focus']。这个事件处理函数是通用的,也就是不管是什么元素,只要获得焦点,我就根据该元素所具有的class进行变换。好,既然我们可以捕捉到既是x又获得focus的A,我们为什么不能捕捉一个既是x又是y的A呢(A.x.y)。我们可以改成A[class='x y x_y x_focus y_focus']。

这里我们要迈出重要一步。获得focus本身,其实可以看作增加了一个focus伪类(记做pc-focus),所以A[class='x y']获得focus,就得到A[class='x y pc-focus'],按照我们前面的变换,并把既是x又是y并且获得focus的情况(A.x.y:focus)也考虑进来,最后我们可以得到:
A[class='
  x
  y
  pc-focus
  x_y
  x_pc-focus
  y_pc-focus
  x_y_pc-focus
']

如果我们推而广之,就能发现这其实就是在IE下模拟多类的效果。对于任何一个class='a b c d…'的情况,我们只要把class的值改为a b c d … a_b a_c a_d … b_c b_d … c_d … a_b_c a_b_d … a_c_d … a_b_c_d,然后在写css的时候遵循一定的规则:
多个class按照字母顺序书写,即把X.a.b.c, X.a.c.b, X.c.b.a统一写做X.a_b_c
按照优先级顺序书写,即先写X.a然后写X.a_b和X.c_d,最后写X.a_b_c_d;
就可以了。

实际上,我正在酝酿一个开源项目,遵循这个思路,并把所有这些变换自动化(通过htc来override className属性,能把class的转换自动化;自动产生focus,hover,first-child等伪类;通过css解析处理工具,能把CSS2.1的多class selector自动转换为等价的IE形式)。这样,开发者就可以自由一点的写CSS,而不必束手束脚了。相比较Dean Edwards的IE7,这种方法所提供的改进有限,并不能给予开发者完整的CSS2/3的支持,但是边际效用很大,更轻量级。因为本质上是使用IE自己的引擎,而不是自己实现的CSS Parser,所以对Ajax应用是透明的,在实际应用中性能也几乎没有损失。因此这一方案应能适用于大型商业网站。

虽然我的项目尚处于计划阶段,但是原理是很简单的,任何人都可以付诸实践。
分享到:
评论
9 楼 protti 2007-10-26  
有的时候  麻烦的是老项目  而不是新项目  

维护的时候受到的约束最多了
8 楼 hax 2007-08-16  
birdjavaeye 写道
相当不错,鼓励鼓励!

是否能让for IE的部分由IEPatch自动生成呢?


这正是目标之一。不过最近还没有时间开展项目。
7 楼 birdjavaeye 2007-08-16  
相当不错,鼓励鼓励!

是否能让for IE的部分由IEPatch自动生成呢?
6 楼 hax 2007-08-15  
被评为精华帖了,所以把原文又润色了一下,现在应该更容易理解了。

另附上在淘宝UED blog上的comments更新部分:

问题是怎么理解“复杂化”。实际上,更好的CSS支持必然能让事情变得更合理、简单、清晰、可维护。我之前对于table布局的讨论,对于多class selector在实际中运用的讨论,其实都说明了这一点。

那么什么让CSS变复杂了?其实正是那许多trick。我仍旧是拿段王爷的这个分隔线的例子。请比较我提出的方法和(稍作变形的)段王爷的方法:

ul { padding:0; margin:0; }
li { display:inline; }
li ~ li:before { content:url(sep.bmp); }

vs

ul { padding:0; margin:0; overflow:hidden; zoom:1; }
li {
display:inline;
background:url(sep.bmp) left center no-repeat;
zoom:1;
margin-left:-8px;
padding:0 8px;
}

sep.bmp是一个8px宽的图片。两段代码效果上是几乎等价的。并且对代码做了最大简化,去除了所有无关代码。

很容易看出哪个更简单。这种简单,不仅在于代码的量的多少,而且更关键的是在于对于意图的表述。前者很清晰。后者就算一个css老手,也得花点精神才看的出来。而且后者带有一些意图之外的副作用,例如获得了hasLayout,又如对background, padding和margin的征用。请注意,这还是一个我们都认为很合用,也蛮清晰的trick。如果是更复杂的trick呢?

当我们有大量样式的时候,css trick的累加所造成的可维护性的下降,是很可观的。这是“意图丢失”所造成的。当然良好的注释可以改善一下这个状况。

所以,请考虑一下意图清晰的CSS2.1代码所带来的好处。

然后我们考虑前者如何运用到IE中。
第一个方法,不管IE,在IE下自然graceful degradation。
第二个方法,用Dean Edwards的IE7或类似项目。
第三个方法,也是按照我计划项目(暂时称作IEPatch)的思路下的做法(虽然目前还是设想,但绝对可以实现):

ul { padding:0; margin:0; }
li { display:inline; }
/*因为暂时不能模拟sibling selector,所以换用了一种写法*/
li::before { content:url(sep.bmp); }
li:first-child::before { content:'’; }

/* for IE */
li { pe-before:enabled; }
li .pe-before { content:url(sep.bmp); }
li.pc-first-child .pe-before { content:''; }

设想中的IEPatch会使用一个htc重载className,元素会自动获得一些伪类,例如pc-first-child等价于:first- child伪类。同时,实现content属性,并通过一个扩展的pe-before属性来表示是否为一个元素产生::before伪元素。注意,这听上去很困难,但实际上确实是可以实现,而且不会对呈现性能造成影响的。

这样,上述的三行代码就表示:对于li启用::before伪元素,li的::before伪元素使用sep.bmp作为其内容,li如果是first-child则::before伪元素内容置空。

看上去for IE的部分似乎复杂了,但是其实并不复杂,因为它与前面的标准CSS代码是一一对应的(除了用作辅助的第一句)。

表示意图的代码,始终只有一份,就是以标准CSS书写的那份。for IE部分,只是遵循规则就可以得到的简单的转换,而不是复杂的trick。如果有工具帮助,更是很容易自动产生的(实际上可以纳入到整个项目的building流程中)。

这是我对于“复杂性”的理解。

CSS实践之所以复杂,绝大多数时候来自于trick。也正是trick,导致CSS实践有时候甚至变成了近乎于艺术的工作。然而,发现一个 trick所获得的快感其实是一种慢性毒药,因为CSS本身不该是这样的。它不应该让我们被迫带着枷锁跳舞,它应该易于使用,很好的反映我的设计意图。
5 楼 i_love_sc 2007-08-15  
我很感兴趣。希望快点看到楼主的劳动成果啊。
4 楼 hax 2007-08-15  
默认上支持最多3个class,也就是在CSS中可以写:
X.a.b.c,但是不支持X.a.b.c.d。对class属性里的个数没有限制(当然越多性能越差,但是没有css上class个数的影响大)。

也可以改变默认设置到4个、5个甚至更多,但是性能会有所下降。我测试下来,在目前的中低端机器上,8个以上就不太可以接受了。

但是实践当中,css中多class,一般都是2个或者3个,很少人会用到更多。所以这个方法在实践中是有效的。
3 楼 hax 2007-08-15  
一个雏形实现(以下代码以LGPL条款发布):
class.htc
<public:component lightweight="true">
<public:property name="className" get="getClass" put="setClass"/>
<public:method name="hasClass"/>
<public:method name="addClass"/>
<public:method name="removeClass"/>
</public:component>
<script>
if ('nodeType' in element) {
	MultiClassPatch.setClass(element, element.getAttributeNode('class').value);
}
function hasClass(name) {
	return MultiClassPatch.hasClass(element, name);
}
function getClass() {
	return MultiClassPatch.getClass(element);
}
function setClass(name) {
	return MultiClassPatch.setClass(element, name);
}
function addClass(name) {
	return MultiClassPatch.addClass(element, name);
}
function removeClass(name) {
	return MultiClassPatch.removeClass(element, name);
}
</script>


class.js
var MultiClassPatch = (function ()  {

	var MULTICLASS_MAX = 3;

	function getClass(e) {
		return e.getAttributeNode('class').value.
			replace(/\S*?[.]\S*/g, '');
	}

	function setClass(e, value) {
		log.trace('entry setClass: $1 $2', nodeInfo(e), value);
		
		var values = value.replace(/\S*?[.]\S*/g, '').split(/\s+/);
		var a = [];
		for (var i = 0, size = values.length, cache = {}; i < size; i++) {
			var v = values[i];
			if (!cache.hasOwnProperty(v)) {
				a.push(v);
				cache[v] = true;
			}
		}
		if (a.length > 8 /*avoid javaeye bug*/ ) {
			log.warn('Performance Warning: Too many ($1) class values.', a.length);
		}
		
		var mc = [];
		var s = a.join(' ');
		mc.push(s);
		
		names = a;
		
		var n = names.length < MULTICLASS_MAX ? names.length : MULTICLASS_MAX;
		while(n > 1) {
			n--;
			var a = [];
			for (var i = 0; i < names.length; i++) {
				var name = names[i];
				var hasName = new RegExp('(?:^|\\s+)(?:\\S*[.])?(?:' + escapeRegExp(name) + ')(?:[.]\\S*)?(?=\\s+|$)', 'g');
				a.push(s.replace(hasName, '').replace(/\S+/g, name.replace('$', '$$') + '.$&'));
			}
			s = a.join(' ');
			mc.push(s);
		}
		
		var v = mc.join(' ');
		e.getAttributeNode('class').value = v;
		
		log.trace('exit setClass');
	}

	function hasClass(e, value) {
		var re = new RegExp('(?:^|\\s)' + escapeRegExp(value.replace(/\s+/g, '.')) + '(?:\\s|$)');
		return re.test(e.getAttributeNode('class').value);
	}

	function addClass(e, value) {
		if (hasClass(e, value)) return false;
		setClass(e, getClass(e) + ' ' + value);
		return true;
	}

	function removeClass(e, name) {
		var hasName = new RegExp('(?:^|\\s+)(?:\\S*[.])?(?:' + escapeRegExp(name) + ')(?:[.]\\S*)?(?=\\s+|$)', 'g');
		var attr = e.getAttributeNode('class');
		attr.value = attr.value.replace(hasName, '');
	}

	function escapeRegExp(s) {
		return s.replace(/[(){}.*+?^$|\[\]\\]/g, '\\$&');
	}

	return {
		getClass:getClass,
		setClass:setClass,
		hasClass:hasClass,
		addClass:addClass,
		removeClass:removeClass,
		VERSION:'2.0'
	};
	
})();




测试代码:

<html>
<head>
<script src="../src/scripts/lang.js"></script>
<script src="../src/scripts/system.js"></script>
<script src="../src/scripts/log.js"></script>
<script src="../src/class.js"></script>

<style id="ie-class-patch">
* html body, * html body * { behavior:url(../src/class.htc); }
</style>

<style>
body { color:white; background:gray; }
.a { background:navy }
.b { background:maroon; }
.c { border:thin dotted; }
html>body  .a.b { background:green; }
* html     .a\.b { background:green; }
html>body  .a.b.c { border-width:medium; }
* html     .a\.b\.c { border-width:medium; }
html>body  .c.d.e { color:yellow; }
* html     .c\.d\.e { color:yellow; }
</style>
</head>
<body>
	<p class="a">a  background:navy</p>
	<p class="b">b  background:maroon</p>
	<p class="c">c  border:thin dotted</p>
	<p class="a b">a b  background:green</p>
	<p class="a b c">a b c  background:green; border:medium dotted</p>
	<p class="a b c d">a b c d  background:green; border:medium dotted</p>
	<p class="a b c d e">a b c d e  background:green; border:medium dotted; color:yellow</p>
	<p class="
		test-multiple-class-01 test-multiple-class-02 test-multiple-class-03
		test-multiple-class-04 test-multiple-class-05
	">multiple class 5</p>
	<p class="
		test-multiple-class-01 test-multiple-class-02 test-multiple-class-03
		test-multiple-class-04 test-multiple-class-05 test-multiple-class-06
	">multiple class 6</p>
	<p class="
		test-multiple-class-01 test-multiple-class-02 test-multiple-class-03
		test-multiple-class-04 test-multiple-class-05 test-multiple-class-06
		test-multiple-class-07
	">multiple class 7</p>
	<p class="
		test-multiple-class-01 test-multiple-class-02 test-multiple-class-03
		test-multiple-class-04 test-multiple-class-05 test-multiple-class-06
		test-multiple-class-07 test-multiple-class-08
	">multiple class 8</p>
	
	<p class="
		test-multiple-class-01 test-multiple-class-02 test-multiple-class-03
		test-multiple-class-04 test-multiple-class-05 test-multiple-class-06
		test-multiple-class-07 test-multiple-class-08 test-multiple-class-09
		test-multiple-class-10 test-multiple-class-11 test-multiple-class-12
		test-multiple-class-13 test-multiple-class-14 test-multiple-class-15
	">multiple class 15</p>
</body>
</html>


注意,这里没有包括所有代码,但是包括了关键的概念和实现方法。
2 楼 hax 2007-08-15  
性能是一个关键问题。因此我不会做任何dom循环,也不会用任何js selector。而是使用IE本身的能力。

实现上,最核心的一点就是override className。
1 楼 radar 2007-08-15  
我宁愿通过javascript来hack css.
毕竟现在 javascript sector很多,性能\提升都很好.


如果用你的htc方案,整个dom树循环可能避免不了吧?

相关推荐

    CSS网站布局实录 (第二版)PDF版

    1.7 面向现在与未来的设计 1.7.1 Web标准与Web 2.0 1.7.2 用户体验技术 1.7.3 用户体验设计的发展趋势 第2章 XHTML与CSS基础 2.1 XHTML基础 2.2 选择合适的DTD 2.3 选择合适的标签 2.4 给CSS留下接口 2.5 良好的...

    ITRS-2015-Summer-Lecture

    教学大纲讲师抽象的这将是为ITRS设计的面向动手的夏季讲习班。 在本研讨会的最后,学生将构建一个小型Web服务作为最终项目,并获得构建自己的未来服务的能力。 除了基本HTML / CSS / JavaScript,我们还将用作在运行...

    客客威客系统KPPW v2.5 GBK Beta.zip

    客客威客系统KPPW是一款基于PHP MYSQL技术构架的威客系统 ,积客客团队多年实践和对威客模式商业化运作的大量调查分析而精心策划研发,是您轻松搭建威客网站的首选利器。KPPW针对威客 任务模型进行了细致的分析,...

    FED2:CMDA前端开发2

    是作为对阿姆斯特丹应用科学大学通信与多媒体设计课程评估而创建的最终产品(课程内容与未来的课程有所不同)。 课程内容 HTML5 API 本地存储 JavaScript 没有 jQuery 面向对象编程 路由 模板制作 AJAX 调用 数据...

    FLEX从入门到精通.pdf

     2.4 Flex生态系统未来的扩展  2.5 本章小结   第3章 ActionScript3.0基础  3.1 核心语言概念  3.1.1 基本语法  3.1.2 保留关键字  3.1.3 变量和数据类型  3.2 控制结构  3.2.1 条件语句  3.2.2...

    客客威客系统KPPW 2.2 GBK Beta.zip

    客客威客系统KPPW是一款基于PHP MYSQL技术构架的威客系统 ,积客客团队多年实践和对威客模式商业化运作的大量调查分析而精心策划研发,是您轻松搭建威客网站的首选利器。KPPW针对威客 任务模型进行了细致的分析,...

    WEB设计大全

    导航理论与实践 &lt;br&gt;5.1 导航 &lt;br&gt;5.2 我在哪? &lt;br&gt;5.2.1 Web上的精确定位:URL &lt;br&gt;5.2.2 网页和站点标签 &lt;br&gt;5.2.3 网页、站点的样式和位置 &lt;br&gt;5.2.4 我曾到过哪? &lt;br&gt;5.3 我能去...

    WEB设计大全(part2)

    导航理论与实践 &lt;br&gt;5.1 导航 &lt;br&gt;5.2 我在哪? &lt;br&gt;5.2.1 Web上的精确定位:URL &lt;br&gt;5.2.2 网页和站点标签 &lt;br&gt;5.2.3 网页、站点的样式和位置 &lt;br&gt;5.2.4 我曾到过哪? &lt;br&gt;5.3 我能去...

    jQuery权威指南-源代码

    期待它能超越旧俗,引领技术未来的发展方向。近年来,Web开发领域的新技术和新工具层出不穷,它们的出现极大地推动了Web开发技术的发展,其中jQuery的诞生在Web技术的发展进程中具有划时代的意义。 jQuery发布于...

    客客威客系统KPPW 2.2 UTF8 Beta

    全新前后台UI风格设计,严格遵循W3C网页标准,采用HTML5、CSS3开发技术,KPPW2.0的整站设计充分考虑了威客行业用户体验需求和对未来拓展性开发的页面支持性。 商城交易模型化,拓展性更强 威客商城是以卖方...

    Node与Express开发.pdf

    4.1 最佳实践 ................................................................................................................................... 26 4.2 版本控制 ..........................................

Global site tag (gtag.js) - Google Analytics