CSS难点布局:逆转z-index

更新,为何要有一个“z-index”可控的要求呢?有这些原因:

  • 动态加载的时候,需要重新算出来第一个到最后一个的z-index;
  • 如果元素数很多,而页面有弹窗之类的话,要为浮层设置1000000这样的大数字吗?如果是这样的话,整个页面的z-index都失控了;
  • 同理,hover一个元素的时候,也需要动态算出它的z-index。
  • 请着重考虑项目的“大小不定、数量不定”的要求啦~

出题啦出题啦,SF好久没有稍微有点挑战的CSS问题了。

需求:

有一系列大小不定、数量不定的元素,它们相对垂直居中,相互交叠20px,像这样:

图片描述

在hover的时候,把hover的元素提到最前面:

图片描述

(假如粉色的元素正在被hover,它应当出现在最前面)

  1. 你可以控制的内容:后端模板、CSS、JS
  2. 尽可能不要用JS来干布局的活
  3. 额外的要求,你能做到z-index可控吗?

提示,正常情况下,使用负margin会是这样的结果(遮盖顺序正好相反):

图片描述

阅读 11.6k
7 个回答

看了上面各位的答案,感觉都好赞。
不过再回头看了一下,在问题中的描述是这样的:

有一系列大小不定、数量不定的元素,它们相对垂直居中,相互交叠20px...

在这个问题中,有这么几点:

  • 大小不定
  • 数量不定

这两个条件应该都不是什么问题,然后这里还有就是:

  • 它们是相对垂直居中的
  • 叠加部分是20px

那么叠加部分是20px的话,比较简单,使用margin负值的方式,就可以得到了,然后还有一个情况就是垂直居中,这个垂直居中应该不是用margin-top或者padding-top的方式直接顶下来,而是很自然的那种垂直居中。

那么这个垂直居中的方式用什么方式呢?如果是用CSS3的方式,直接一个元素就可以搞定了,如果是用inline-block的方式,那么要考虑每个元素之间的间距,如果是用table的方式,那么就要考虑元素叠加的问题要怎么解决和处理。

从这个情况来分析,我个人的看法是,只要处理了这个垂直居中的问题,其他的都不是什么问题,因为在CSS中,只要在:hover的时候,改变z-index值就可以了。根据DOM的情况,后面的元素肯定会叠加在前面的元素上,就像问题中描述的一样。

bVknXF

而如果通过给每个元素设定一个z-index的方式来实现,感觉也并不是十分可取,不够灵活。那么我所能想到的就是把元素排列方式翻转一下,然后再通过:hover来改变z-index,应该就OK了。

http://jsfiddle.net/b9gtqLb8/

额……好像超过很多的时候,因为float:right出现了点小状况,好吧,再来一个减少点内容的看看。

http://jsfiddle.net/b9gtqLb8/2/

公子大大的思路太贊,考慮了高大上的情況。
我們這些菜菜就只能考慮更一般的情況了。。。

題主想做的,無非是讓計算機自動完成原本需要人工來完成的事情。
無奈 CSS 並不是編程語言~
所以只好藉助 SCSS 來完成了。

HTML:

<main>
    <section></section>
    <section></section>
    <section></section>
    <section></section>
    <section></section>
    <section></section>
</main>

SCSS:

$maxz: 0;

@function color($n, $off) {
    $m: ($n + $off) % 3;
    $q: $n % 6;

    @if $q < 3 {
        @if $m == 0 {
            @return 255;
        } @else {
            @return 0;
        }
    } @else {
        @if $m < 2 {
            @return 255;
        } @else {
            @return 0;
        }
    }
}

@mixin zindex($z) {
    $maxz: max($z, $maxz);
    z-index: $z;
}

@mixin orgeful($n, $count) {
    section:nth-child(#{$n}) {
        background: rgb(color($n, 0), color($n, 1), color($n, 2));

        $off: $n % 5 * 10px;
        width: $off + 100px;
        height: $off + 100px;
        margin-top: (50px-$off / 2);
        margin-right: -20px;

        @include zindex($count - $i);
    }
}

section {
    width: 100px;
    height: 100px;
    background: #f00;
    float: left;
    position:relative;
}

@for $i from 1 through 6 {
    @include orgeful($i, 6);
}

section:hover {
    z-index: $maxz;
}

http://jsfiddle.net/88vtghmh/2/

先按默认的情况来设置,z-index为1、2、3、4、5……hover的时候设为100,这样不行?

公子已经给出两个思路了,我来补充一下我之前的解决方案吧。

这个问题最困难的一点就是z-index的处理方式。

1

先说最常规的思路。如@limichange和@公子的第一版答案,用到正序排列的z-index.

此外,公子都需要为每个元素设置一个class,为每个class赋予一个z-index……这样的缺点不用我说了吧?
就算是使用nth-child和SCSS的预处理方案,也是很不靠谱的,难道我们要为可能发生的0~100000000的场景各赋予一个值吗?
其实一个想当然的误区在于,所有的样式设置都需要在CSS中。可能平时我们要做到表现和结构的分离,但是在这个问题中,我们难以做到这个,因为样式和元素顺序强相关,而现在CSS没有一个简洁的方案能够让我们挂钩元素顺序和样式。
——为何不稍微考虑一下inline-style的作用呢?
这个方案发展下来,应该是这样的:

<ol>
<li class="item" style="z-index: 4"></li>
<li class="item" style="z-index: 3"></li>
<li class="item" style="z-index: 2"></li>
<li class="item" style="z-index: 1"></li>
</ol>
<style>
.item { display: inline-block; position: relative; margin-left: -20px; }
.item:hover { z-index: 10000000000!important; }
</style>

这个时候不得不用到了!important :(

好的,我们考虑剩下的事情:
- hover时,需要把元素提到最前的时候,就把它的z-index加3;
- 动态创建元素的时候,需要给第一个到最后一个元素重新赋予z-index;
- 页面弹窗的时候,动态计算z-index的最大值,并赋予一个更大值。

这个方案的缺点一目了然。

2

如何让z-index可控呢?

这里要了解层叠上下文的特性。z-index也是有层叠特性的。

在上一个方案中,为ol加入一个z-index: 1; position: relative;即可把下面的下属收拾得服服帖帖的,不再僭越其它浮层的z-index。

3

如何让表现不依赖于js呢?我们忘了z-index负值的作用。每个元素可以都是负值,同时可以简单地将它的z-index值设为1来提到最前面。

以下是我的DEMO,也是我最终采用的方案:

<ol class="container">
<li class="item" style="z-index: -1">
<li class="item" style="z-index: -2">
<li class="item" style="z-index: -3">
<li class="item" style="z-index: -4">
<li class="item" style="z-index: -5">
<li class="item" style="z-index: -6">
<li class="item" style="z-index: -7">
<li class="item" style="z-index: -8">
</ol>
<style>
.container { position: relative; z-index: 1; }
.item { display: inline-block; vertical-align: middle; margin-right: -20px; position: relative;}
.item:hover { z-index: 1!important; }
</style>

http://jsfiddle.net/4arw2upq/7/

更新,由于浏览器计算鼠标处于哪个元素的碰撞算法在我们的case中有些buggy,所有实际应用中是通过mouseenter和mouseleave动态增删类来替代:hover的。
但在负margin部分鼠标的碰撞算法依然失效,因此,如果可以自己实现鼠标碰撞的话,这个问题就比较圆满了。

4

启发式CSS?之前我没有想到方案,但是一个demo启发了我:本身这就是一个有序列表,我们需要的正是计数的特性。如果浏览器支持的话,这将是最简洁优雅的方案,甚至都不需要改html:

.container { position: relative; z-index: 1; counter-reset: z; }
.item { display: inline-block; vertical-align: middle; position: relative; counter-increment: z; z-index: calc( 0 - counter(z) ); }
.item:hover { z-index: 1; }

可惜,如我们所知,浏览器不支持:(

More

肯定还有更多方案的,欢迎大家踊跃填写

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏