真・懒

订阅 Twitter GitHub 联系

标准的、语义的、Unobtrusive 的页签切换

页签的流行

自从 Yahoo!的首页引进页签(tab, 见下图)之后,这种可用性极佳的方式越来越受欢迎,用户也逐步习惯和喜欢上它,因为它可以在原有的空间上增加更多的可用信息,而且只需切换,不需刷新整个页面,浏览更舒畅。很多网站接受并使用,如新浪等。

页签样例

页签的标记结构

那么,让我们来看看这些页签后的代码。

新浪完全不考虑什么标准,就是表格嵌套,我们略过不提。Yahoo!的 XHTML 形式是这样的:

<div>
  <ul>
    <li>页签1</li>
    <li>页签2</li>
    ...
  </ul>
</div>
<div>
  <div>内容1</div>
  <!--它们可能由 Ajax 载入-->
  <div>内容1</div>
  ...
</div>
...

符合标准,但却没有语义。页签和相应内容没有任何关联。也就是说,在没有 CSS 展现的情况下,用户并不晓得页签究竟对应哪一块内容。而且就 JavaScript 实现来说,必须对元素定义更多的 id 或者 class 作为调用钩子(hook),容易造成代码冗余。有人对此作出改良,使用连接元素的 hash(即 #号后的字段)跟内容进行关联,即下面这种形式:

<div>
  <ul>
    <li><a href="#content1">页签1</a></li>
    <li><a href="#content2">页签2</a></li>
    ...
  </ul>
</div>
<div>
  <div id="content1">内容1</div>
  <!--它们可能由 Ajax 载入-->
  <div id="content2">内容1</div>
  ...
</div>
...

这种方式对于机器来说,确实找到了关联点,而且用户点击的时候,也能在 hash 的作用下(传统说法中的「锚点」)调到相应的内容区块。有进步,但还是不够语义。

语义,语义,语义!

理想中标准的、语义的 tab 代码应该是怎么样的呢?在我看来,应该是这样:

<dl>
  <dt>页签1</dt>
  <dd>内容1</dd>
  <dt>页签2</dd>
  <dd>内容2</dd>
</dl>

当然,我认为并不是 Yahoo!的设计师/开发者并不了解语义,可能是由于某些特殊的需求在这种代码下可能会实现不了,只好采取折衷方案。是的,在这种代码形式下,语义虽能充分体现,但是要实现页签的表现形式,确实是一个难题。

解决之道

首先,请打开我们的 Demo 页面,先自行分析一下。如果您使用 Firefox,可以尝试把 CSS 样式禁用进行「欣赏」(如果您装了 Web developer toolbar,您可以 CTRL + SHIFT + S)。继续。

解决 dt 的横排

dtdd 交错,如何能够使得 dt 排在一行上?well,理论不分析太多,要使它们在一起,我们假设 dd 不存在。这样的话,使用 float 就能排在一起。既然 dd 不能不存在,ok,那么让它们脱离文档流,如何做?position:absolute; 就可以了。但是 IE6 有问题,wtf . 我的解决方法是,使用 JavaScript 把所有的 dt 凑一块,这样严重伤害了语义,但这只是一个浏览器问题,而且是在有 JavaScript 的时候才产生语义问题,阿弥陀佛,辩证法认为事物都具两面性。

解决 dd 的自适用高度

对于已经 position:absolute; 了的 dd,无论是理论还是实践,使用纯 CSS 都没有解决方法。同样,我使用了 JavaScript 来动态计算它的每一次高度,然后赋予整个 dl

局限性与缺点

这样做保证了标准、语义、Unobtrusive。但对于少部分拥有能解析 CSS 的先进浏览器但却关闭了 JavaScript 的用户来说,极有可能会被不能自适用高度的页签下的内容区块挡住了跟随在后的信息。

s2uTab

很高兴我能写出一些实用的 JavaScript,以上所提到的,我将之命名为 s2uTab -- 偏要解释的话,它就是 Standard, Semantic, Unobtrusive Tab 的缩写。首先,它很小,不依赖于任何库,在 IE6+, opera 9+, Firefox 2+均通过测试(若您有 Safari,务必帮忙测试一下,谢谢)。其次,灵活,除了 dl 外无须任何钩子,且页签数目灵活没有限制。再次,您可以指定页签切换的事件形式,可以指定初始的页签是哪个。

用法

您可以为 window.onload 添加如下函数:

s2uTab(页签, 事件类型, 初始页签);

其中,页签指 dl 元素的引用,如果您传入的是字符串,则返回 id 是改字符串的 dl 引用;事件类型是指,页签的激活是点击还是鼠标悬停,传入 click 或者 mouseover(注意:没有"on"!)即可;初始页签是指您在初始化页面时需要激活的页签,注意,为符合编程习惯,请从 0 算起。

请多多参考我们的例子