我的天!又在几个知名组件库发现相同的bug!(组件库zIndex管理方案)

前言

这篇文章的重点是 z-index 管理方案,主标题是标题党吸引点流量,请谅解,希望我用高质量的方案对比来消解你对标题党的怒火 😅。

有些人可能觉得 z-index 有啥难的,这其实是一个很经典的前端难题了。我们先看看以下几个组件库如何让它们的 z-index 管理出现异常。

以下问题在国内 3 个知名组件库,阿里的 ant design,腾讯的 tdesign 和 semi-design 出现。后续也会简单说一下他们的 z-index 管理方案的原理以及出现问题的原因。

这里字节的 arco design 是解决了这个问题的。后面也会讲 arco 是怎么设计的。

我们先看如何复现问题,有在线案例。

首先我们把一个两个 Button 组件放在一起,如下图:

在线链接

image.png

然后第一个 Button 组件是触发弹出 Modal 框的,第二个按钮是类似 Tooltip 组件,我们让这个弹层永远显示,这样好复现 bug。

然后点击 Open Modal 的 button 按钮,出现 Tooltip 把 Modal 的黑色蒙层遮盖了的问题。

image.png

你可能说这算啥 bug,出现几率很低,我们再来一个?

看看 ant vue 有没有问题(只要你知道他的 z-index 方案,你能想出 n 个方法让他出问题)

在线链接

image.png

在分析产生问题的原因前,大家是否想过一个问题,你可以看看你用的组件库,把一些弹出框组件(例如,modal 组件,tooltip 组件,message 组件等等)都渲染在了 dom 流的哪个地方?

答案是 body 下,如下图

image.png

你思考过为什么要这么做吗?比如上图,正常情况,不是应该按钮在哪里,这个对应的弹框跟按钮在一起吗,渲染到 body 下干嘛?

这其中一个重要的原因就是为了管理 z-index。

层叠上下文

为了说明这个问题,我们还要弄清楚一个概念,叫层叠上下文?我想问大家,zIndex 越大一定就在最高的层级吗?

答案是 no!

举个例子

<style>
    .box1,
    .box2 {
        position: relative;
        z-index: 1;
    }

    .child1 {
        width: 200px;
        height: 100px;
        background: #168bf5;
        text-align: right;
        position: absolute;
        top:0;
        z-index: 99999;
    }

    .child2 {
        width: 100px;
        height: 200px;
        background: #32c292;
        position: absolute;
        top:0;
        z-index: 1;
    }
</style>
</head>

<body>
    <div class="box1">
        <div class="child1">child1</div>
    </div>

    <div class="box2">
        <div class="child2">child2</div>
    </div>
</body>

以上代码,我们可以看到 child2,zIndex 是 1,child1 的 zIndex 是 99999,按道理来说,child1 的 zindex 更大,它应该展示在 child2 上面,可是结果如下:

image.png

原因就是 box1 和 box2 都创造了层叠上下文(如果有 zindex 为数字 + 非 postion:static 布局会产生层叠上下文,还有很多条件也能创造层叠上下文,这里就不细说了)

box1 和 box2 的层叠等级一样,所以遵循谁后写谁在上面,所以 box2 永远在 box1 上面

所以 box2 里面的元素,是永远比 box1 里面的元素层级更高的。

那么 child1 和 child2 比较根本没有意义,因为他们并不在一个层叠上下文中,只有在一个层叠上下文中,比较 zindex 才有意义。

为什么放到 body 下

因为我们可以看到,业务代码有可能会在很多隐蔽的地方产生层叠上下文,这个组件库是无法控制的,所以如果大家把很可能产生遮盖效果异常的组件都放在 body 上,就相当于大家在一个层叠上下文中了,可以更好的控制遮盖问题。

特殊情况

从上面来看,放在 body 下,大家都在一个层叠上下文中,那么就会遵循谁后出现,谁在层级之上的效果,但是总有一些常见的情况是不想要这样的,比如:

  • 我有一个 message 组件,弹出消息,3 秒之后消失,在 1 秒的时候我就点击 modal 框,但是我们遵循谁后点击,谁在层级上,那么 modal 组件就把 message 组件覆盖了,这并不是我们想要的。

所以一般情况,对于 message 和 notification 的弹出消息,我们总是希望他们层级是最高的。

  • 还有就是我在文章开始复现的一个问题,就是因为 modal 的 z-index 没有 tooltip 的层级高

这下大家知道为什么产生问题的原因了吧。如何解决呢?我们看看 bootstrap5 的方案:

bootstrap zindex 设计

$zindex-dropdown:                   1000;
$zindex-sticky:                     1020;
$zindex-fixed:                      1030;
$zindex-modal-backdrop:             1040;
$zindex-offcanvas:                  1050;
$zindex-modal:                      1060;
$zindex-popover:                    1070;
$zindex-tooltip:                    1080;

这个简单看看就好,我觉得有点过时了,因为 bootstrap 是在 jquery 那个年代的流行产物,并不会将所有弹出框类似的组件渲染到 body 下.

但是这起码说明一个问题,就是按照 bootstrap 这个标准,至少很少有弹出层异常的问题,为什么是很少有呢?

因为特殊情况基本上都是层叠上下文导致的,这种特殊情况只有组件单独导入 zindex 适配业务需求。

通过设置 z-index 层级的方案

类似 bootstrap,通过对特殊弹框类组件设置不同的 z-index 来避免遮盖问题,我们列举了以下方案:

ant design

// ant-design/components/style/themes/default.less
/* z-index列表, 按值从小到大排列 */
@zindex-badge: auto;
@zindex-table-fixed: 2;
@zindex-affix: 10;
@zindex-back-top: 10;
@zindex-picker-panel: 10;
@zindex-popup-close: 10;
@zindex-modal: 1000;
@zindex-modal-mask: 1000;
@zindex-message: 1010;
@zindex-notification: 1010;
@zindex-popover: 1030;
@zindex-dropdown: 1050;
@zindex-picker: 1050;
@zindex-popoconfirm: 1060;
@zindex-tooltip: 1070;
@zindex-image: 1080;

在我的组件库里,因为 popover,dropdown,tooltip,selelct 类型的下拉框都属于 popup 组件,所以跟 ant design 略有不同,他们都是一个层级。

为什么我能试出来 ant vue 的 bug,大家可以看 ant design 中@zindex-dropdown: 1050,然后@zindex-popover: 1030,那么意味着,在同一个层叠上下文中,我先触发 dropdown,再触发 popover,那么 popover 一定是在 dropdown 底下的,所以会产生 bug。

后来我看 ant design5 学聪明了,不支持传入组件,只能传入数组了。。。我的组件也是这么做滴,嘿嘿,当然不仅仅是因为这个 bug,后期要为低代码平台做铺垫,所有传入的数据最好都是普通数据,比如数组,对象,而不是 react 组件。

全局管理器方案

elementUI 将弹窗层级管理收敛到了一个入口 PopupManager 中,涉及 zIndex 层级的弹窗组件实例都需要注册到 PopupManager 中。

简单来说,就是用一个全局的对象记录当前最高的 zindex,然后下一个比这个更高,简单来说如下:

class ZIndexManager {
  constructor() {
    this.zIndex = 1000; // 初始的 z-index 值
    this.zIndexMap = new Map(); // 用于存储元素和对应的 z-index 值
  }

  getNextZIndex() {
    this.zIndex += 1;
    return this.zIndex;
  }

  registerElement(element) {
    const nextZIndex = this.getNextZIndex();
    this.zIndexMap.set(element, nextZIndex);
    this.updateElementZIndex(element, nextZIndex);
  }

  unregisterElement(element) {
    this.zIndexMap.delete(element);
  }

  updateElementZIndex(element, zIndex) {
    element.style.zIndex = zIndex;
  }
}

const zIndexManager = new ZIndexManager();
export default zIndexManager;

但是我觉得,很多场景并不是说我需要后面出现的弹层一定要比前面的层级高。

我们之前也说了,message(也就是 toaster)肯定是最高层级的,我们不希望 modal 比它还高,所以这个方案我觉得还能更好。

改进 ant design 方案

在我看来,ant deisgn 的方案稍微改一下,基本上就使用百分之 95%以上的场景了,特殊场景用户自己去单独给组件传入 z-index 或者改变层叠上下文的层级,也就是自定义设置了。

以下层级由低到高:

  • Affix
  • Drawer, Message, Modal,modal-mask, popup 相同层级(从而让后出来的在层级最上面)
  • notification
  • message

上面的 popup 包含很多,比如 select 所有的类似下拉框组件(比如 picker),tooltip,dropdown 等等

arco design 方案

字节的 arco design 在这方面我觉得是国内做的比较好的,文章初的两种 bug 均对它无效。

字节的处理基本上跟我上面改进的方案差不多,但是它只对 Modal 和 Drawer 组件内部的所有组件的 z-index 进行了+1 处理

为什么要这么做,我们要看下 arco 的 z-index 方案。

  // z-index
  '--z-index-popup-base': 1000,
  '--z-index-affix': 'calc(var(--z-index-popup-base) - 1)', // 999
  '--z-index-popup': 'var(--z-index-popup-base)', // 1000
  '--z-index-drawer': 'calc(var(--z-index-popup-base) + 1)', // 1001
  '--z-index-modal': 'calc(var(--z-index-popup-base) + 1)', // 1001
  '--z-index-tooltip': 'var(--z-index-popup-base)', // 1000
  '--z-index-message': 'calc(var(--z-index-popup-base) + 3)', // 1003
  '--z-index-notification': 'calc(var(--z-index-popup-base) + 3)', // 1003
  '--z-index-image-preview': 'calc(var(--z-index-popup-base) + 1);', // 1001

以上是我的最开始的 z-index 方案,就是从 arco 借鉴而来,但是我们发现上面有什么问题呢?modal 的 zindex 是 1001,popup 的 zindexshi 1000,意味着我先打开 modal 框,然后 modal 框里有一个 popup 按钮,再触发 popup 按钮后,显示的文字居然回到 modal 框后面(我的组件库目前有这个 bug)

所以 acro 怎么避免这个情况呢,例如,在 modal 框里,会把 modal 框里传入的组件所有 index 重新设置为当前 modal 的 zindex + 1,所以 arco 避免了这种 bug。

而我怎么做呢,我只要把 modal 的 z-index 改成和 popup 一样,这不就天然是谁后出现,谁在上面了吗,巧妙的达成了和 arco 一样的效果。

求个 star

做组件库教程不易,求个 star,哈哈,

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值