使用 Sass 管理 z-index 更好的解决方案
最近我们看到有关于 Sass 管理 z-index
几篇文章:
- Jackie Balzer在Smashing Magazine网站上发布的《Sassy z-index Management for Complex Layouts》
- Doug Avery在Viget网站上发布的《Sass and z-indexes: a (slightly) better way》
- Chris Coyier在CSS-Tricks网站上发布的《Handling 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
);
有两件事情:
- 我喜欢将
mixins/functions
要用的变量单独配置在一个文件中,比如_config.scss
。你喜欢的话可以随意将他们移到z()
函数里面。 - 你可以随意添加、删除和更新你需要的任意
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/
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论