读万卷书行万里路

css实现九宫格(一)

发布时间:2016/1/22 19:15:0
分类:frontend/css
    全文约1.8k
    预计需要7分钟

前段时间,我的 leader Henry在群里面分享了一道一淘的面试题。 题目非常的有趣,忙完前阵的工作之后突然记起,也尝试做了一下。

9个元素,每个50*50px,排成九宫格
默认是border颜色为blue,hover到格子上变成red(兼容到IE6)

css题目

做成九宫格大家都会,但题目的陷阱就在hover上。鼠标hover到格子4,格子5时,其实他们“共用”了一条边。由于是纯css实现的,我们不可能说用js去动态改变dom,因此怎样实现“公用边”就成为了难点。

尝试的过程:

  • 我的第一个想法,用“叠加”的方式实现“公用边”;
  • 后来的想法,用table的border-collapse实现“公用边”;
  • 在table想法的基础上改进;
  • 一种更简便的做法,不需要border,见九宫格(二)

##我的第一个想法

先做做看,尝试永远是第一步。我将9个div都设置了5px的border,排成了九宫格,添加了hover,这时候初始的效果是:
初始效果
这样其实格子之间的距离是两倍border(10px)。需要再将中间的一竖(2,5,8)设置margin-left:-5px;margin-right:-5px;,再将中间的一横(3,4,5)设置margin-top:-5px;margin-bottom:-5px;,这样等于是强制把格子间的距离“拉”到5px。
把格子间的距离“拉”到5px
到这一步,简单的九宫格是完成了,但hover之后会发现,格子的边会被挡住(格子5的下边和右边分别被格子8和格子6挡住)。因为这里“公用边”的思路准确来说是“重合边”,是用负值的margin强制定位的。而我的解决方式是hover时添加z-index:999,让hover到的格子在最上层显示而不会被挡住。同时,不要忘记在9个div的css里面添加一句让z-index生效的position: relative;,具体原因看这里

代码君:
1.html:

<div id="test0">
    <div>1</div>
    <div class="lr_indent">2</div>
    <div>3</div>
    <div class="tb_indent">4</div>
    <div class="lr_indent tb_indent">5</div>
    <div class="tb_indent">6</div>
    <div>7</div>
    <div class="lr_indent">8</div>
    <div>9</div>
</div>

2.css:

#test0&#123;
    margin: 30px;
    width: 200px;
    height: 200px;
&#125;
#test0 div&#123;
    width: 50px;
    height: 50px;
    float: left;
    background: #eee;
    border: 5px solid #00f;
    text-align: center;
    line-height: 50px;
    color: #090;
    position: relative;
&#125;
#test0 .lr_indent&#123;
    margin-left: -5px;
    margin-right: -5px;
&#125;
#test0 .tb_indent&#123;
    margin-top: -5px;
    margin-bottom: -5px;
&#125;
#test0 div:hover&#123;
    border: 5px solid #f00;
    z-index: 999;
    background: #eee;/*必须加这一句,在IE6,7有bug*/
&#125;

思考:这样的方式好吗?不够好。
这才是9宫格,如果是16,25,…,81个格子,设置margin缩进的人力代价是很高的。
兼容性,在IE6,7下,负值margin在hover时候有bug。

ie6,7下,hover时负值margin-left不起效

##后来的想法

经过第一次尝试,我得到一个经验:要用一种通用的方法去解决“公用边”,而不是分别设置.lr_indent和.tb_indent。
随即我想到了表格。作为table,它有个很突出的属性,就是合并border,css里面的设置为border-collapse:collapse;。ok,这就是key point。

按照这个思路,我简单的编写了代码,一开始我把hover定位到td上面去,发现hover时也会出现第一个想法中“挡住”的情况。而且,去将td的position改变,再添加z-index的方法是不可能有用的(z-index不会起效)。

我的方法是在td中包含一个span,把hover定位到span中去,td设置为position:relative;,span设置为position:absolute;,这时候的hover就可以设置让span的border不被挡住展示了。
代码君又来了:
html:

    <div id="test1">
        <table>
            <tr>
                <td><span>1</span></td>
                <td><span>2</span></td>
                <td><span>3</span></td>
            </tr>
            <tr>
                <td><span>4</span></td>
                <td><span>5</span></td>
                <td><span>6</span></td>
            </tr>
            <tr>
                <td><span>7</span></td>
                <td><span>8</span></td>
                <td><span>9</span></td>
            </tr>
        </table>
    </div>

css:

    *&#123;
        margin:0;
        padding: 0;
    &#125;
    table&#123;
        border-collapse: collapse;
    &#125;
    #test1 td&#123;
        width: 50px;
        height: 50px;
        background: #eee;
        position: relative;
        border: 5px solid #00f;
        text-align: center;
    &#125;
    #test1 td span&#123;
        color: #090;
        display:block;
        width: 50px;
        height: 50px;
        position: absolute;
        top: 0;
        left: 0;
        line-height: 50px;
    &#125;
    #test1 td span:hover&#123;
        border: 5px solid #f00;
        margin-top:-5px;
        margin-left: -5px;
    &#125;

别忘了span在hover时,必须设置一个负的margein-topmargein-left,以保证红色边框恰好定位在格子四周。见css君最后的片段。假如不设置,你看到的将是这样:
未设置span:hover的margin
本以为已经大功告成了,在IE中测试却让我傻了眼:
IE7下的情景
(ps:作为前端一枚,我已经做好了妥妥的心理准备,但此情此景还是让人喷出一口老血……)

##改进,改进

说实话,table和div之争这么多年,大家都在页面中用越来越多的div,而越发的鄙视table,反而对table的熟悉程度反应了前端们的基础是否扎实。吃一堑长一智,这句话特别适用于在table中翻江倒海的亲们。

改进!

首先这个bug(也无所谓是不是bug,算是浏览器的差异性吧)我知道,在table的td里面设置了position:relative;就会在IE中出现这样的情况。注意是所有的IE哦,包括IE10。而根据第二个思路,最后的hover定位的元素为span,它本身设定为position:absolute;它的父级元素必须得设置position:relative;才能完成题目功能,这是毋庸置疑的。

既然现在span的父级td不能设置position:relative;,我就在它们之间添加一个div,用来做span的容器。

代码君再一次来了:
html:

    <div id="test2">
        <table>
            <tr>
                <td><div><span>1</span></div></td>
                <td><div><span>2</span></div></td>
                <td><div><span>3</span></div></td>
            </tr>
            <tr>
                <td><div><span>4</span></div></td>
                <td><div><span>5</span></div></td>
                <td><div><span>6</span></div></td>
            </tr>
            <tr>
                <td><div><span>7</span></div></td>
                <td><div><span>8</span></div></td>
                <td><div><span>9</span></div></td>
            </tr>
        </table>
    </div>

css:

    *&#123;
        margin:0;
        padding: 0;
    &#125;
    table&#123;
        border-collapse: collapse;
    &#125;
    #test2 td&#123;
        width: 50px;
        height: 50px;
        background: #eee;
        border: 5px solid #00f;
        text-align: center;
        vertical-align: top;
    &#125;
    #test2 td div&#123;
        position:relative;
        width: 50px;
        height: 50px;
    &#125;
    #test2 td div span&#123;
        color: #090;
        display:block;
        width: 50px;
        height: 50px;
        position: absolute;
        top: 0;
        left: 0;
        line-height: 50px;
    &#125;
    #test2 td div span:hover&#123;
        border: 5px solid #f00;
        margin-left: -5px;
        margin-top: -5px;
    &#125;

OK,效果达成!
最终完成的效果
可以猛点这里看看demo

最后吐槽,不对,总结一下下:

先到IE上去测,再转到其它浏览器,以少走弯路,这叫擒贼先擒王-_-!;
win8的metro布局最近挺流行的,有时候table比div好用;
IE君,你真是……此处省略1024个字

这个系列打算写两篇文章,下一篇介绍另外一种更简洁的方法。:)

目录
文章作者:CavinHuang
文章链接:http://blog.zukmb.cn/posts/7d0bca61/
版权声明:转载请注明来自CavinHuangのBlog
instagram图片拉取小经验