本文以解决问题的思路和方法为线索进行讨论,基础的 CSS 知识就不赘述了。另外本文仅涉及基本布局,关于 JS 特效的实现有机会再另行撰文。
接到项目后的第一件事,不是切图,也不是写代码,而是分析设计稿。
首先我们会发现,设计稿中最特别的地方在于各菜单项贴合图片的曲线,呈现不规则的排列。那么似乎为各菜单项分别定义外边距(或文本缩进)就可以实现了。然而事情并没有这么简单,UI 设计师提醒我们,这个菜单需要实现动态的“手风琴效果”,二级菜单不论展开与否,所有菜单项都必须自然地贴合图片轮廓。因此,各菜单项的位置不能定死。看来我们要换一种思路。
当菜单展开与折叠时,用 JS 来计算各菜单项应该出现的位置并重新定位,这是我想到的第一个方法。很显然,这是可行的。但此时我想给自己一个小挑战,不依赖 JS,完全用 CSS 来实现这个布局要求。表现层的事,JS 越少插手越好
――这是我给自己定下的一个基本原则。而且这也不是很紧迫的任务,就让我再多思考一下吧。
文字在纵向自然流动,在水平方向自动避开某个区域,这不得不让我们联想到浮动的某些特性。事实上这个问题很像是 CSS 教材中常常会提到的“异形排版”――把文字灌入到一个不规则的容器当中。它的实现原理就是用一些无语义的空元素,通过浮动来占据某些区域,文字流动时会自动避开这些区域,从而呈现不规则排版。简单分析一下,我们眼前的这个项目似乎也可以用到这个方法。那么,开始干吧!
首先我们需要确定这些浮动占位元素的尺寸。打开 Photoshop,用路径工具勾出曲线的大致轮廓,限定文字流动区域的右边缘。
示意图(已缩小至50%):
利用 Photoshop 的网格和参考线工具将曲线进行栅格化,即量化为多个长方形。因为 CSS 目前还只能处理矩形元素,我们只能用矩形来模拟曲线的形状。我采用的量化精度是 5px。我觉得这是一个适中的数字,精度更高也没有意义了,反而增加无谓的资源消耗。
示意图(原比例局部图):
度量并记录一下曲线栅格化后得到的数据(也就是这些长方形的高宽尺寸)。我们稍后将利用这些数据在页面中建立占位元素。
在下面的示意图中,我用半透明的黄色矩形来表示这些占位元素的尺寸和位置。这些矩形之间的间隙是实际上是不存在的,仅作示意。
示意图(已缩小至50%):
图片编辑工作到此结束,下面打开文本编辑器,开始根据导航的结构编写 HTML 代码。
平时我们在制作导航时,都是采用无序列表,这几乎已经成为公认的法则了,那么今天也不例外。这里的二级导航,与下拉菜单有相似之处,我决定通过无序列表的嵌套来表达菜单的层级关系。那么结构代码大致应该是这个样子的:
<div id="dv_nav">
<h2>导航</h2>
<ul id="ul_nav">
<li>
<h3><span>洗护系列</span></h3>
<ul>
<li><a href="###">赋活水养</a></li>
<li><a href="###">动感卷曲</a></li>
<li><a href="###">……</a></li>
</ul>
</li>
<li>
<h3><span>造型系列</span></h3>
<ul>
<li><a href="###">珠光亮泽</a></li>
<li><a href="###">酷炫凌乱</a></li>
<li><a href="###">……</a></li>
</ul>
</li>
<li>
<h3><a href="###">促销信息</a></h3>
</li>
<li>
<h3><span>秀发护理小TIPS</span></h3>
<ul>
<li><a href="###">洗发知多少</a></li>
<li><a href="###">护发怎么办</a></li>
<li><a href="###">型发知多少</a></li>
</ul>
</li>
<li>
<h3><a href="###">VIDEO</a></h3>
</li>
</ul>
</div>
简单解释一下为什么会写成这样。
首先导航部分是一个完整的逻辑区块,于是将它放进一个 <div>
标签之中,并命名(ID)。除了命名之外,我觉得还需要一个更明显的区块标识,于是我用了一个 <h2>
,毕竟它不可能大过整个页面的标题。可能有同学会说,这个标题在设计稿中是不存在的,没关系,我们可以用 CSS 把它隐掉。它存在的意义主要还是在于标记结构,此外裸奔的时候你也会看到它。
写完标题,接下来就是导航的正文了,前面已经说过,采用无序列表。每个一级菜单都对应一个 <li>
,同时一级菜单项采用 <h3>
进行标记。二级菜单项同样也是采用无序列表来组织,它们都紧随对应的一级菜单项 <h3>
之后,包含在一级菜单的 <li>
之中。不论一级二级菜单,导航中的每个链接自然需要都采用 <a>
标签来标记。(比照上面的代码来阅读本段,应该会更容易理解。)
值得注意的是一级菜单项 <h3>
的内部文字,有些用了 <a>
标签,而有些是 <span>
。这里需要强调一点,在写结构的时候,它是什么,就让它是什么。如果某一段文字确实就是链接,那么就用 <a>
;如果这段文字只是看起来有点像链接(比如可以响应点击动作)但却并没有链接到某个 URL 时,就不应该用 <a>
,我们完全可以使用 CSS 让它具有可点击的视觉表现。所以在这里,部分一级菜单项只是一个标题,虽然最终我们要通过点击它来展开和收缩二级菜单,但它本身并不是一个链接,我就没有让它成为一个链接。当然为了便于应用样式和绑定事件,我在 <h3>
内部嵌套了一层 <span>
标签作为钩子备用。
好了,现在我们来看一下这个结构在无样式的情况下是什么样子的。嗯,看起来很有条理,功能也很完整。
示意图(为节省空间,启用了浏览器的缩放功能):
接下来就是重头戏了,我们开始为页面添加样式。
添加样式的第一步当然是必要的 CSS Reset。在这个小演示里我就不导入外部文件了,直接写两句简单点的达到目就行。当然,如果是正式项目,还是需要引入一套专业的适合自己的 CSS Reset。
* {margin: 0; padding: 0;}
li {list-style: none;}
接着定义一下页面的基本样式,比如页面背景色、文字字体与颜色、链接下划线等等。
body {
font: 12px/1.2 'Microsoft YaHei', Verdana, Arial, Helvetica, sans-serif;
color: #ddd;
background: black;
}
a:link, a:visited {text-decoration: none;}
a:hover, a:active {text-decoration: underline;}
然后,开始为 #dv_nav 写一些基本样式,比如基本尺寸、背景图片等等,为后续的布局搭建一个范围。
#dv_nav {
padding-top: 30px;
width: 500px;
height: 540px;
background: url(img/bg-1.jpg) no-repeat;
}
刚刚上面提到,我用了一个 <h2>
来标记结构,但它不需要在页面中出现,于是我们让它消失。
#dv_nav h2 {display: none;}
接下来开始定义菜单项的样式。刚刚做了 CSS Reset,所有菜单项之间的间隔都消失了,它们会紧密地挤成一堆。所以,我们首先设置一级菜单区段之间的间隔。(这里用内边距或外边距都可以,不过我自己习惯优先使用内边距,姑且视为个人积累下来的布局思维模式吧。)
#ul_nav li {padding-bottom: 24px;}
由于 IE6 不支持子元素选择符,我们在这里采用了后代选择符。不妙的是,这条规则也会对二级菜单项产生影响,所以我们要消除这个副作用。同时,我们还要定义二级菜单项之间的间隔。(我在这里使用的是外边距,当然你也可以试试换成内边距,只要效果达到并且布局方式健壮就行。)
#ul_nav li li {
padding-bottom: 0px; /** 清除上一条规则对二级菜单的影响 **/
margin-top: 10px;
}
我们再继续给一、二级菜单项应用一些文字样式,让它们看起来更加层次分明,同时更加接近设计稿的要求。
#ul_nav h3 a, #ul_nav h3 span {
font-size: 16px;
font-weight: 700;
color: #aaa;
}
#ul_nav li li a {color: #999;}
好了,基本样式定义完成了。不过为了让大家看清楚各元素的相对关系,我们再增加一些额外的样式。毕竟不是每位同学都是在 Firefox + Firebug 环境下进行设计和调试,对吧?
#ul_nav h3 {background: #555;}
#ul_nav li li {background: #222;}
打开浏览器预览一下,页面应该是这样的。
示意图:
做完了基本的导航布局之后,我们要实现导航的异形排版。在 Step 3 中,我们收集了占位元素的尺寸数据,现在我们要把它们添加到页面之中。
根据浮动的原理,在文档流中,浮动元素必须在文本之前,文本才会环绕浮动元素流动。于是,在结构代码中,占位元素必须添加在导航之前。这样代码的结构就变成了这样:
<div id="dv_nav">
<h2>导航</h2>
<!--=======================================================-->
<div id="dv_placeholder">
<div class="n1"></div>
<div class="n2"></div>
<div class="n3"></div>
...
</div>
<!--=======================================================-->
<ul id="ul_nav">
...
</ul>
</div>
照例解释一下结构代码。(为了让大家看得更清楚,我在新增代码的头尾使用了注释进行标注。)
根据 Step 3 格栅化的结果,我们总共需要 27 个占位元素。如果把这些元素直接插入到区块容器(#dv_nav)之下,则感觉有些凌乱。于是我在它们的外围加了一层 <div>
并命名,让结构代码看起来更有条理。即使它们都是纯粹为了布局而存在的无语义元素,也要让它们有条不紊地存在。――当然如果你没有这种代码洁癖的话,就让它们随地躺着也没关系。
占位元素的标签我选择用 <div>
,如果你喜欢用 <p> <span> <b>
等等其它标签也是可以的,对最终效果没有影响。由于各占位元素的尺寸不一,我为它们设置了不同的类名。“n1
”、“n2
”等等意为序号,是我自己习惯的设计模式,或许也可以算作框架的一部分吧。
结构写好之后,来写样式。首先让这些占位元素一律向右排,并且各自独占一行。
#dv_placeholder div {
float: right;
clear: right;
}
接着为每个占位符设定尺寸。
#dv_placeholder .n1 {width: 185px; height: 10px;}
#dv_placeholder .n2 {width: 195px; height: 10px;}
#dv_placeholder .n3 {width: 200px; height: 10px;}
...
写到这里,我们终于遭遇到第一个浏览器 bug ―― IE6 的块级元素默认高度 bug。这里不多解释,直接用 18 个字节解决它。
#dv_placeholder div {
font: 1px/1 serif; /** 清除块默认高度 @ IE6 **/
}
同样,为了让大家看清楚各元素的相对关系,我们让占位元素显示为半透明的黄色。
#dv_placeholder div {
background: yellow;
opacity: 0.2; filter: Alpha(Opacity=20);
}
这样,占位元素就安排好了。我们看一下浏览器中的情况。
示意图:
经过上一步的精心设置,占位元素貌似已经占据了合适的位置。(或许你会有一点小疑问:包裹了占位元素的 #dv_placeholder 这个元素在哪里?事实上由于它内部的占位元素均已浮动(脱离文档流),这样对于文档流的布局来说,#dv_placeholder 就是一个空元素,空 div 元素是不占据高度的。所以,虽然在结构上它包含了多个占位元素,但在表现层,它是零高度的。当然这里还有一个技巧就是避免让它在 IE6,7 下触发 hasLayout,否则 IE6,7 会错误地让它闭合浮动的占位元素,从而将导航正文挤到占位元素的下方。)
现在我们距离理想的效果还有一步之遥――导航还没有排列为弧形,那么我们给它来一点儿 CSS 魔法。
#dv_nav {
text-align: right; /** 各菜单项右对齐 **/
}
大家请看好,接下来,就是见证奇迹的时刻!
示意图:
所有各级菜单都乖乖地右对齐,并且紧贴着占位元素,呈现出平滑的弧形排列效果。
不过,慢着,你在 IE6 下可能发现了一点小问题――菜单文字跟占位元素之间好像隔了一点什么,没有真正紧密地贴着。似乎我们再次撞上了 IE6 的 bug ――这一次是著名的“3px 浮动 bug”。如果你不是那么追求完美,完全可以无视这一点偏差。但如果你已经下定决心精益求精,那么一行代码也足以解决它。
#dv_placeholder div {
_margin-left: -3px; /** 应付3px bug @ IE6 **/
}
好了,大功告成!为了验证这种布局方式是否是可扩展的,你可以自行增减菜单项。(您可以另存“精简演示”自行修改测试。)
最后我们去掉那些表明元素相对关系的附加样式,还它真正的面目。
示意图:
这样就结束了?
是的。
这篇文章的核心是讲述布局思路。喜欢抠下代码直接走人的朋友可能会有点失望,这篇文章所实现的效果跟成品相比还是有很大差距――样式不够美观,更不要说手风琴效果。但是,我更愿意向你展示它洗尽铅华之后的内在之美。在我眼中,那华丽的衣衫就好像天边的浮云一样……
相对于布局技巧来说,手风琴 JS 特效可能稍显平淡。打开“完整演示”所调用的 JS 文件,你可能会发现代码极为简洁,仅仅对元素类名的进行操作,完全没有涉及表现层的设置。真正的表现层代码完全存在于 CSS 文件中,这样的低耦合降低了开发复杂度和维护成本。有机会撰文详述,不知道您是否感兴趣。希望您能喜欢 CSS魔法 带来的更多教程。
感谢您看完本文,我期待您的批评和建议!
作者:CSS魔法