使用Sass管理z-index更好的解决方案

本文由大漠根据Hugo Giraudel的《A Better Solution for Managing z-index with Sass》所译,整个译文带有我们自己的理解与思想,如果译得不好或有不对之处还请同行朋友指点。如需转载此译文,需注明原作者相关信息http://www.sitepoint.com/better-solution-managing-z-index-sass/

——作者:Hugo Giraudel

——译者:大漠

最近我们看到有关于Sass管理z-index有好向篇文章:

刚好前几天把Jackie Balzer在Smashing Magazine网站上发布的《Sassy z-index Management for Complex Layouts》一文翻译成中文了:《Sass管理复杂的z-index》。——@大漠

虽然Chris Coyier去年那篇文章最初帮助我解决了管理z-index,但并不怎么得心应手。从技术的角度出发,我在这里提出一个更简单、易用的方案。

因此通过这篇文章,我将系统的引导您,并且向您展示是如何创建管理z-index,以及如何让你在项目中更好的使用它。

为什么要管理z-index的值?

首先我们来回答这个问题:为什么我们要用Sass来管理z-index的值?我们没有看到无数文章如雨后春笋般的冒出来介绍使用Sass管理padding,对不对?那么为什么有关于z-index的会这么多呢?

那么,它必须要这样做,因为z-index很容易造成错误。此外,它接受无穷小到无穷大之间的任意一个值,在很多场合之下,他会搞砸你的事情。

因此,使用像9999999的值,让其在你的屏幕上随意出现。你可能不知道这是怎么一回事,但使用一个扩展工具是非常有趣的。在这种情况之下,使用CSS预处理器可以帮助你搞定这件事情。

概念是什么?

关于这个话题的所有文章介绍的概念几乎都是一样:创建一个可以带有关键词的函数,而且这个关键词可以通过Sass的map映射得到值。这个映射出来的值就是z-index需要输出的值。

Jackie Balzer使用了列表,Doug Avery使用列表和map的混合,Chris Coyier使用了map。事实上,Chris Coyier的解决方案与我的最终方案非常接近,只不过他的解决方案不太适合大系统环境下使用。

最终我们会这样使用:

.element {
  z-index: z("modal");
}

什么要使用函数而不使用混合宏?

Doug Avery在他的文章介绍的方法是使用混合宏(mixin)。混合宏与函数最大区别在于使用方式:

.element {
  @include z("modal");
}

虽然我知道这是个人喜好的问题,但我真的建议:如果只传一个CSS属性,尽量不要使用混合宏。这个功能就是一个很好的示例。

开始编写

因此,我们需要一个函数可能接受一个参数。随意称呼他,我把他称作$layer:

@function z($layer) {
  // ... 
}

这个函数要做些什么呢?它会在map上寻找给定的参数,看它是否被映射到z-index值。如果是,慢返回值,否则返回一个错误信息。所以我们要创建一个map

$z-layers: (
  "goku":            9001,
  "shoryuken":       8000,
  "default":            1,
  "below":             -1,
  "bottomless-pit": -9000
);

有两件事情:

  1. 我喜欢将mixins/functions要用的变量单独配置在一个文件中,比如_config.scss。你喜欢的话可以随意将他们移到z()函数里面。
  2. 你可以随意添加、删除和更新你需要的任意keys/values

现在回到我们的函数中:

@function z($layer) {
  @return map-get($z-layers, $layer);
}

在这一点上,我们没有比map-get($z-layers,...)做得更好的方法。这个东西实际上也非常的酷,每次手工一遍又一遍的输入的确让人很烦。

如果该键值存在map中,则会返回映射到的值的索引值。如果该键值不存在,则会返回null。每当一个属性的值为null的时候,Sass的编译不会输出。

所以,如果调用一个未知的键值,Sass只是默默的不输出,这样做不太理想。让我们使用@warn指令发出警告信息,让开发者(也就是你)知道关键值不在map中:

@function z($layer) {
  @if not map-has-key($z-layers, $layer) {
    @warn "No layer found for `#{$layer}` in $z-layers map. Property omitted.";
  }

  @return map-get($z-layers, $layer);
}

如果你定义的map中有一个未知的关键词(例如SitePoint),Sass在命令终端编译的时候会输出:

WARNING: No layer found for `SitePoint` in $z-layers map. Property omitted.

是不是很酷。

使用方法

现在,功能已完成,我们应该让他发挥他的功效了。正如文章开头演示的那样,使用方法非常简单:

.modal {
  // ...
  z-index: z("modal");
}

.modal-overlay {
  // ...
  z-index: z("modal") - 1;
}

我的想法是,定义z-index值是需要始终使用这个功能。如果你只是有时候使用,有时候不使用,那么很容易搞死你。

另外,我觉得尽量让这个map保持较轻量级。层级你添加的越多,你的z-index管理也复杂。我建议成对的出现,通过关键词就可以映射出对应的值,这样更便于管理。

使用嵌套上下文加强使用功能

你不可能不知道z-index的值不是绝对的。他们都是相对于自己层叠的内容有关。这也意味着,你试图将内容A的元素在内容B的上面,但内容B却在内容A的上面,然后你就算是设置超过九千的值也将是无效的

这段话理解起来蛮拗口的。我的理解是这样的:在你的页面中有两个区块A和B。B区块的z-index值高于A区块,现在你要将A区块内的元素层级要高于B区块。此时就算你将A区块内的元素的z-index定义的值超过九千也将是无效的。——@大漠

注:有关于层叠上下文和z-index值的有关知识,请务必阅读Philip Walton写的这篇文章

现在,如果我们想使用系统认识的层叠的上下文,我们可以在map中嵌套map。举例来说明,如果我们的modal里面有新的层叠上下文,我们想按元素的顺序来设置,那么只需要更新map

$z-layers: (
  "goku":            9001, 
  "shoryuken":       8000,
  "modal": (
    "base":           500,
    "close":          100,
    "header":          50,
    "footer":          10
  ),
  "default":            1,
  "below":             -1,
  "bottomless-pit": -9000
);

问题是,使用map-get()我们不能在嵌套的map中轻意的获取到我们需要的值。不过,值得庆幸的是,只需要创建一个这样的函数就能实现:

@function map-deep-get($map, $keys...) {
  @each $key in $keys {
    $map: map-get($map, $key);
  }

  @return $map;
}

这样就可以了,是不是很简单?现在,我们就可以这样使用我们的modal模块了:

.modal {  
  position: absolute;
  z-index: z("modal", "base");

  .close-button {
    z-index: z("modal", "close");
  }

  header {
    z-index: z("modal", "header");
  }

  footer {
    z-index: z("modal", "footer");
  }
}

这样将产生这样的结果:

.modal {
  position: absolute;
  z-index: 500;
}

/* This is `100` in the modal stacking context */
.modal .close-button {
  z-index: 100;
}

/* This is `50` in the modal stacking context */
.modal header {
  z-index: 50;
}

/* This is `10` in the modal stacking context */
.modal footer {
  z-index: 10;
}

上述示例中使用map嵌套map的时候,并不能编译成功,已向作者询问解决方案。不过通过Sass方面的大神@wo_is神仙提供了一个更简单的解决方案:

@function z($layers...) {
  $keys: '';

  @each $layer in $layers {
    $keys: $keys + '.' + $layer;
  }
  $keys: str-slice($keys, 2);

  $output: map-find($z-layers, $keys);
  @if $output == null {
    @warn 'No layer found for `#{inspect($layers)}` in $z-layers map. Property omitted.';
  }

  @return $output;
}

// Before: map-get(map-get(map-get($map, a), b), c)
// After: map-find($map, 'a.b.c')
@function map-find($map, $keys) {
  @while str-index($keys, '.') {
    $index: str-index($keys, '.');

    // Child elements
    $map: map-get($map, str-slice($keys, 0, $index - 1));
    @if type-of($map) != map {
      @return null;
    }

    // Rest keys
    $keys: str-slice($keys, $index + 1);
  }

  @return map-get($map, $keys);
}

有了这个函数功能之外,我们可以通过作者介绍的一样,使用map嵌套map实现。来看一个简单的示例

$z-layers: (
  'goku': 9001,
  'shoryuken': 8000,
  'modal': (
    'base': 500,
    'close': 100,
    'header': 50,
    'footer': 10
  ),
  'default': 1,
  'below': -1,
  'bottomless-pit': -9000
);

@function z($layers...) {
  $keys: '';

  @each $layer in $layers {
    $keys: $keys + '.' + $layer;
  }
  $keys: str-slice($keys, 2);

  $output: map-find($z-layers, $keys);
  @if $output == null {
    @warn 'No layer found for `#{inspect($layers)}` in $z-layers map. Property omitted.';
  }

  @return $output;
}

// Before: map-get(map-get(map-get($map, a), b), c)
// After: map-find($map, 'a.b.c')
@function map-find($map, $keys) {
  @while str-index($keys, '.') {
    $index: str-index($keys, '.');

    // Child elements
    $map: map-get($map, str-slice($keys, 0, $index - 1));
    @if type-of($map) != map {
      @return null;
    }

    // Rest keys
    $keys: str-slice($keys, $index + 1);
  }

  @return map-get($map, $keys);
}

.modal {
  position: absolute;
  z-index: z(modal, base);

  .close-button {
    z-index: z(modal, close);
  }

  header {
    z-index: z(modal, header);
  }

  footer {
    z-index: z(modal, footer);
  }
}
.goku {
  z-index: z(goku);
}

这个编译出来的结果:

.modal {
  position: absolute;
  z-index: 500;
}
.modal .close-button {
  z-index: 100;
}
.modal header {
  z-index: 50;
}
.modal footer {
  z-index: 10;
}

.goku {
  z-index: 9001;
}

正是我需要的结果。

不过我会时刻关注作者的相关回答,同时会更新新的解决方案。如果大家有更好的解决方案,也可以通过评论与我们一起分享。

更新

今天收到原作的回信,其提供了另一种解决方案

@function map-has-nested-keys($map, $keys...) {
  @each $key in $keys {
    @if not map-has-key($map, $key) {
      @return false;
    }
    $map: map-get($map, $key);
  }
  
  @return true;
}

@function map-deep-get($map, $keys...) {
  @each $key in $keys {
    $map: map-get($map, $key);
  }
 
  @return $map;
}

@function z($layers...) {
  @if not map-has-nested-keys($z-layers, $layers...) {
    @warn "No layer found for `#{inspect($layers...)}` in $z-layers map. Property omitted.";
  }
 
  @return map-deep-get($z-layers, $layers...);
}

整个示例代码:

$z-layers: (
  "goku":            9001, 
  "shoryuken":       8000,
  "modal": (
    "base":           500,
    "close":          100,
    "header":          50,
    "footer":          10
  ),
  "default":            1,
  "below":             -1,
  "bottomless-pit": -9000
);

@function map-has-nested-keys($map, $keys...) {
  @each $key in $keys {
    @if not map-has-key($map, $key) {
      @return false;
    }
    $map: map-get($map, $key);
  }
  
  @return true;
}
@function map-deep-get($map, $keys...) {
  @each $key in $keys {
    $map: map-get($map, $key);
  }
 
  @return $map;
}
@function z($layers...) {
  @if not map-has-nested-keys($z-layers, $layers...) {
    @warn "No layer found for `#{inspect($layers...)}` in $z-layers map. Property omitted.";
  }
 
  @return map-deep-get($z-layers, $layers...);
}
.modal {  
  position: absolute;
  z-index: z("modal", "base");
 
  .close-button {
    z-index: z("modal", "close");
  }
 
  header {
    z-index: z("modal", "header");
  }
 
  footer {
    z-index: z("modal", "footer");
  }
}
.goku {
  z-index: z("goku");
}

编译出来的CSS:

.modal {
  position: absolute;
  z-index: 500;
}
.modal .close-button {
  z-index: 100;
}
.modal header {
  z-index: 50;
}
.modal footer {
  z-index: 10;
}

.goku {
  z-index: 9001;
}

未来

美好的未来终将有一天会到来,我们只需要在CSS中使用变量。这样我们不需要这一切。相反,这会发生什么?

:root {
  --z-goku:            9001;
  --z-shoryuken:       8000; 
  --z-modal:            500;
  --z-default:            1;
  --z-below:             -1;
  --z-bottomless-pit: -9000;
}

.modal {
  z-index: var(--z-modal);
}

看到了?几乎是同样的东西,只不过var()替代了z()--key替代了key。稍长一些,而且多了会引起混乱,因为这些都是单个变量,而不是通过map来映射。不过他能正常工作,而且保持它的原始性。

结论

我不知道你是怎么想的,但我认为,在Sass中管理z-index,这是一种很好的方法。它不仅好管理z-index,而且还可以使用map映射出需要的z-index值。我们从最后的一个示例就可以看出效果。

就是这样。在Sassmeister上放了一个演示示例。从现在开始,没有理由不将这个功能运用到你的项目当中。

译者手语:整个翻译依照原文线路进行,并在翻译过程略加了个人对技术的理解。如果翻译有不对之处,还烦请同行朋友指点。谢谢!

出处:

英文出处:http://www.sitepoint.com/better-solution-managing-z-index-sass/

中文译文:http://www.w3cplus.com/preprocessor/better-solution-managing-z-index-sass.html