z-index层级管理方案

2,211 阅读4分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情

为什么需要管理z-index

多人开发的复杂项目中,对于定位元素(如弹窗、下拉或者固定的头部),我们很难知道别人写了多高的z-index,有时候想弹窗一个弹窗,却发现别人写了 z-index: 9999999999,于是我们想要覆盖它,只能在后面再加一个9,长此以往,层级将会变得凌乱难以维护。

要解决的问题

  • 统一一个地方管理z-index
  • 校验用法是否正确

方案一:scss的list取下标

定义一个scss数组,数组的值是对应层级的命名:

$elements: header, footer, dialog;

设置z-index时,我们通过scss函数 index 获取数组下标。

.dialog {
    z-index: index($elements, dialog); // 3
}

如此,我们只需要规定书写样式时,统一用 elements,后续维护elements,后续维护 elements 即可,很容易看出,z-inde越大需要放到约后面,当有其他z-index需要插入到前面时,编译时会将所有引用 $elements 的index都更新到。

错误警告:当引用了一个不存在的z-index命名时,我们希望输出警告给开发者

@function z($list, $element) {
    $z-index: index($list, $element);

    @if $z-index {
        @return $z-index;
    }
    
    @warn '#{$list} 中存在#{$element}层级';
    
    @return null;
}

缺陷: 层级定义不灵活,根据数组的index来确定z-index,当你想定义比较大的层级时(如99),几乎是不可能的了,所以这个方案几乎不能派上实际用途,一般ui组件库的z-index都难以覆盖。

方案二:scss的map取键值

方法二可以视为方法一的升级,相当于用 Object 的键值对代替了数组的下标和值。

先定义一个scss的map变量:

$z-layer: (
  "header": 9,
  "footer": 9,
  "dialog": 99,
);

使用z-index时,使用scss中的map-get方法来获取值:

.diglog {
    z-index: map.get($z-layer, "dialog"); // 99
}

封装成函数

$z-layer: (
  "header": 19,
  "dialog": 99
);

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

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

如此一来,我们可以更自由的定义z-index,随时结合ui组件库的z-index做出调整。

定义通用z-index

如果仅仅根据业务来定义z-index,很容易出现z-index越写越大的情况,所以需要先预设一套z-index规则,限制某类元素最大z-index。

同时,一般项目都会使用组件库,所以这个规则需要配合组件库的z-index来制定,否则会出现组件无法被遮挡的尴尬窘境。

如ant-design-vue,组件层级为:

  伪类元素:1
  affix组件(吸顶):10
  drawer、modal: 1000
  message、notification: 1010
  popover:1030
  dropdown 、cascader、autoComplete、DatePicker、select、TimePicker、treeSelect: 1050
  Popconfirm:1060
  tooltip: 1070
  其他: < 5

结合ant-design-vue层级规范,我们业务中的组件层级可以定义为:==标准值[-区间值]==

  伪类:1[-4]
  页面绝对定位元素 5[-9]
  页面吸顶/固定定位元素:10[-20]
  drawer/modal: 1000[-1009]
  message/notification: 1010[-1029]
  popover:1030[-1039]
  dropContent: 1050[-1059]
  popconfirm: 1060[-1069]
  tooltip: 1070[-1079]
  activityDialog: 1080[-1099] 活动弹窗,同一时间应该保持只存在一个活动弹窗,
  toast:2080[-2099] 反馈提示,应该高于所有
$z-layer: (
  "before": 1,
  "after": 1,
  "absolute": 5,
  "fixed": 10,
  "modal": 1000,
  "message": 1010,
  "popover": 1060,
  "drop": 1050,
  "confirm": 1060,
  "tooltip": 1070,
  "activity": 1080,
  "toast": 2080
);

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

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

添加scss函数到全局

vite通过 css-preprocessoroptions 配置实现全局scss文件配置

vue-cli通过 css.loaderOptions 配饰实现全局scss文件配置

解决stylelint报错

如果安装了stylelint,很可能会出现这样的提示:

Unexpected unknown function "z" (funciton-no-unknown)

我们可以通过 function-no-unknown配置解决:

找到stylelint配置,ignoreFunctions为忽略这个函数的报错,相当于定义了全局函数:

{
  "stylelint": {
    // ...
    "rules": {
      // ...
      "function-no-unknown": [true, {
        "ignoreFunctions": ["z"]
      }]
    }
  }
}

配合stylelint检查

既然制定了z-index规范,当然是希望在书写的时候都通过函数的方法去定义z-index,但是项目成员多的时候,难免会出现直接写 z-index: 9999999;的情况,为此,如果可以通过stylelint去限制直接写数字就最好不过了

通过stylelint的插件 stylelint-declaration-strict-value 可以实现我们的功能:

安装

pnpm add stylelint-declaration-strict-value -D

配置 增加 plugins 和 rules,其中rules 只允许z-index设置为z函数,不能使用变量("ignoreVariables": false,),不能直接设置数值(默认值"ignoreValues": null

{
  "stylelint": {
    // ...
    "plugins": ["stylelint-declaration-strict-value"],
    "rules": {
      // ...
      "function-no-unknown": [true, {
        "ignoreFunctions": ["z"]
      }],
      "scale-unlimited/declaration-strict-value": [
        "z-index",
        {
          "ignoreVariables": false,
          "ignoreFunctions": {
            "z": true
          }
        }
      ]
    }
  }
}

语法提示

如果在编辑代码的时候,提示 z函数,并且把 $z-layer 值作为悬浮选项值,那真的太方便了,可惜没找到这样的vscode插件,也许需要自己去编写一个插件可以实现。

唯一的语法提示来自:SCSS IntelliSense 插件,在输入z()可以提示入参名字。

参考文献:

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

Sass管理复杂的z-index