下面是一个使用HTML的ul标签制作的关于国家区划的组织结构图。
- 中国
- 北京
- 广东省
- 广州市
- 韶关市
- 海南省
- 海口市
- 美兰区
- 龙华区
- 秀英区
- 琼山区
- 三亚市
- 海口市
- 安徽省
- 合肥市
- 安庆市
- United States of America
- Washington
- Florida
我们的目标是,在保持最干净的HTML源码不变的情况下,只使用CSS,将上面的结构图转换为下面的树形效果。
Ready? Go!
先看HMTL的代码。
- <ul class="domtree">
- <li>中国
- <ul>
- <li>北京</li>
- <li>广东省
- <ul>
- <li>广州市</li>
- <li>韶关市</li>
- </ul>
- </li>
- <li>海南省
- <ul>
- <li>海口市
- <ul>
- <li>美兰区</li>
- <li>龙华区</li>
- <li>秀英区</li>
- <li>琼山区</li>
- </ul>
- </li>
- <li>三亚市</li>
- </ul>
- </li>
- <li>安徽省
- <ul>
- <li>合肥市</li>
- <li>安庆市</li>
- </ul>
- </li>
- </ul>
- </li>
- <li>United States of America
- <ul>
- <li>Washington</li>
- <li>Florida</li>
- </ul>
- </li>
- </ul>
遵循惯例,先将ul的margin及padding置0。
- ul.domtree, ul.domtree ul {
- margin: 0;
- padding: 0;
- }
失去了层次结构。需将padding-left设为非零值。同时将li前面的项目图标去掉。
- ul.domtree, ul.domtree ul {
- margin: 0;
- padding: 0 0 0 2em;
- }
- ul.domtree li {
- list-style: none;
- }
上面的图例中,海口市的四个区有最深的层次。为简化思路,我们从最深一级,即美兰区开始思考解决问题的方法。在海口市与美兰区之间应有一条直角线段“└”将它们连起来。
鉴于美兰区是一个li元素,我们先画出其方框看看。
- ul.domtree li {
- list-style: none;
- border: 1px solid black;
- }
现在,方框围住了美兰区,我们需要将此方框推到美兰区的左边。也就是,在美兰区的左边应有一方框。因为上面已经澄清了目标,我们不能动HTML源码,因此我们可以通过CSS的before伪元素来为我们添加这个方框。
- ul.domtree li {
- list-style: none;
- }
- ul.domtree li:before {
- content: '';
- width: 1em;
- height: 1em;
- border: 1px solid black;
- }
先把li元素本身的边框去掉。因为我们只需一个方框而无需实质内容,因此content为空值。长、宽各设为1em的正方形。边框为黑线。
如上图所示,并没有出现一个正方形的边框。我们可以在content中输入实际内容,并较大幅度地增加长、宽值,比较其效果。
- ul.domtree li:before {
- content: 'a';
- width: 10em;
- height: 10em;
- border: 1px solid black;
- }
前面的围绕字符a的边框出现了,但尽管长宽值均设为10em,边框的面积还是不起变化。由于before伪元素的本意是在特定元素之前添加内容,应与该元素并排显示,因此被设定为inline型。而对处于流布局中的inline型设置具体的长宽不会有效果。此外,流布局元素也不能定位。解决的方法是将其脱离流布局即可。
- ul.domtree li {
- list-style: none;
- position: relative;
- }
- ul.domtree li:before {
- position: absolute;
- content: '';
- top: 0;
- left: 0;
- width: 1em;
- height: 1em;
- border: 1px solid black;
- }
因为content的内容隶属于li元素,因此我们先将其父元素li的position设为relative,再在li:before中将position设为absolute,这样可使content的内容脱离原来的流布局,且相对于li元素的位置来定位。注意上面的content已经恢复为空值,且长、宽恢复为1em。
别为上图的边框恰好围住一个汉字而搞晕了,看看图中的Florida,并没有围住单个字符。因此长、宽各为设1em碰巧围住了一个汉字而已。content的内容为空值也证明了边框面积与内容无关,而仅与长、宽值有关。同样,也别因为边框碰巧围住一个汉字而误以为边框会随汉字的移动而移动。再次重申,碰巧而已,这个边框是可以独立移动的。
现在,我们已经有了一个可以独立移动的边框,为了得到“└”的连线效果,还需做以下事情:
- 我们只需保留边框的左边及底边
- 把这两条边往左推至美兰区之前
- 调整这两条边的位置及长度
根据以上思路,调啊调,最终调成下面的数值及效果。
- ul.domtree li:before {
- position: absolute;
- content: '';
- top: -0.1em;
- left: -0.5em;
- width: 0.4em;
- height: 0.615em;
- border-style: none none solid solid;
- border-width: 0.05em;
- }
已经初具雏形了。但海口市下面的四个区应使用一条竖线将它们连通贯穿起来。此外,海口市与三亚市也应连起来。
由于海口市的四个区太密集了,这回换位思考,先看如何将海口市与三亚市连起来。
现在的问题变成:按照上面的思路,如果这条竖线也是通过边框的方式来实现,那么,在CSS中应选择哪个元素进行相应设置?是海口,还是三亚?先假设这条线已经调好相应的位置,则应顺着海南省的南字下边的竖线一直连向三亚市左边的竖线,贯穿了整个海口市所在区域。因此,应选择海口市的li元素。且由于三亚市左边的竖线已经高于其文字上端,因此,三亚市无需再设置竖线。
同样道理,北京连广东连海南连安徽,只需设置北京、广东和海南所在区域的竖线,而安徽无需设置。而对于中国与United States of America,只需设置中国。
找到规律了吗?如果没有,让我们借助图形来洞悉事实的真相。
上图中,红色部分表示需要设置连线的元素,而灰色表示无需设置连线的元素。
其规律是,每一层级的最后一项元素,无需设置连线;不是最后一项元素,则需设置连线。
如何找出最后一项元素,以及,不是最后一项的元素?幸喜CSS提供了这样的选择器。上图的效果所用到的CSS内容如下:
- ul.domtree li:last-child {
- color: gray;
- }
- ul.domtree li:not(:last-child) {
- color: red;
- }
既然已经甄别出来,显而易见,只需对li:not(:last-child)进行设置即可。
我们上面已经使用before的伪元素设置了li元素之前的连线,这回,我们使用其搭档after伪元素设置左边的竖线。严格来说,after伪元素是在元素的后面添加新的内容。但由于我们通过绝对定位的方式,即使是后面的,我们也可将其调至前面来。参照之前的例子,设置CSS如下:
- ul.domtree li:not(:last-child):after {
- position: absolute;
- content: '';
- top: 0;
- left: 0;
- height: 1em;
- border-style: none none none solid;
- border-width: 0.05em;
- }
因为我们只需要一条线,因此无需设边框的宽度,且此边取值于边框的左线。
各个li元素的竖线已经站在其左边待命,但不够长。上面的height取固定值1em,显然不能应付各种长度不一致的的场合,如中国连向United States of America的连线就比海口市连向三亚市的连线要长。
如果说height是从top往下计算的数值,则bottom则是从下向上计算的数值。使用bottom代替height,可使这条直线的长度自动延伸到足以覆盖该元素的整个高度,而不管该元素的具体高度是多少。
- ul.domtree li:not(:last-child):after {
- position: absolute;
- content: '';
- top: 0;
- left: 0;
- bottom: 0;
- border-style: none none none solid;
- border-width: 0.05em;
- }
已经胜利在望了,剩下需做的,就是将left值往左调。最后,将上面显示不同颜色的CSS代码去掉,就可得到下面的效果。
Game Over.
因篇幅不长,这里列出上述CSS的全部源码。
- ul.domtree, ul.domtree ul {
- margin: 0;
- padding: 0 0 0 2em;
- }
- ul.domtree li {
- list-style: none;
- position: relative;
- }
- ul.domtree li:before {
- position: absolute;
- content: '';
- top: -0.1em;
- left: -0.5em;
- width: 0.4em;
- height: 0.615em;
- border-style: none none solid solid;
- border-width: 0.05em;
- }
- /*
- ul.domtree li:last-child {
- color: gray;
- }
- ul.domtree li:not(:last-child) {
- color: red;
- }
- */
- ul.domtree li:not(:last-child):after {
- position: absolute;
- content: '';
- top: 0;
- left: -0.5em;
- bottom: 0;
- border-style: none none none solid;
- border-width: 0.05em;
- }
注::before及:after伪元素始自于CSS2.1,:last-child伪类及:not伪类始于CSS3。因此,本文的实现需要支持CSS3的浏览器支持。