---
title: 浮层按打开顺序取号与吸顶分割边界
description: 不靠写死的大数字排先后：TuffEx 的浮层从同一个计数器按打开顺序取 z-index，抽屉先把计数抬到 10000；吸顶条用 1px 细线而非投影。
---

# 浮层按打开顺序取号与吸顶分割边界

在专业桌面端应用中，随意堆砌 `z-index` 会导致严重的视图穿透与遮挡冲突。TuffEx 不给浮层分固定的层级档位，而是让它们按打开顺序，从同一个计数器取号。

## 消除 z-index 军备竞赛

传统前端工程遇到图层遮挡时，往往随意写下 `z-index: 9999` 或 `99999`。这会导致：
- 下拉菜单穿透系统弹窗。
- 气泡提示被吸顶栏截断。
- 多 Agent 协同编写业务模块时层级彻底失控。

固定的层级档位也解决不了这个问题：弹窗里打开的下拉菜单必须盖住弹窗，抽屉里打开的弹窗又必须盖住抽屉。谁该在上面，取决于打开的先后，而不是组件的种类。

## 按打开顺序取号

TuffEx 的浮层在打开时向 z-index 分配器取下一个号（`next()`，即当前值加 1），所以后打开的总是盖在先打开的上面：

- **取号的组件**：TxModal；基于 TxBaseAnchor 的浮层，如 TxPopover、TxTooltip、TxSelect、TxDropdownMenu、TxContextMenu；以及 TxCommandPalette、TxFlipOverlay、TxPicker、TxLoadingOverlay 和各类 Dialog。TxToastHost 在每次调用 `toast()` 时取号。
- **起点**：计数器从 2000 起步（`DEFAULT_Z_INDEX_SEED`），第一个浮层拿到 2001。不带参数调用 `refresh()` 时，会改读 CSS 变量 `--tx-index-popper`，取两者中较大的那个。
- **抽屉**：TxDrawer 打开时先 `refresh(10000)`，把计数抬到至少 10000 再取号，所以第一个抽屉是 10001，在抽屉里打开的浮层从 10002 往上排。传了 `z-index` 时直接用这个值，并把计数抬到至少这个值。
- **只增不减**：关闭浮层不会退号，下一个浮层照样取更大的号。
- **按应用隔离**：`app.use(Tuffex)` 时为这个应用建一个分配器，没有 Vue 应用的调用方共用模块级的那一个。

业务代码要做自己的浮层时，同样用 `useZIndexAllocator()` 取号，不要写死数字。

:::TuffDemoWrapper{demo="ConceptDepthStackDemo"}
:::

`variables.scss` 里另有 `--tx-index-normal: 1`、`--tx-index-top: 1000`、`--tx-index-popper: 2000` 三个令牌。`--tx-index-popper` 除了作分配器的起点，也是 TxVersionCapsule 面板的层级；`--tx-index-top` 给 TxMotion 的全局进度条用，让它压在所有浮层之下；`--tx-index-normal` 组件没有用到。TxMotion 的全局指针层是例外，固定为 2147483647：它要跟着指针盖过抽屉和对话框，而分配器的号没有上限。组件内部写死的 `z-index` 大多是 −1 到 10 的小数值，只在组件自己的层叠上下文里排先后。

## 吸顶条使用 1px 细线而非投影

当表格表头或操作条固定在顶部、下方内容滚动经过时，**禁止使用弥散投影（Drop Shadow）作为分割**。

```scss
// 错误写法：阴影让吸顶条漂浮在半空，破坏了滚入内容首行的清晰度
.sticky-bar { box-shadow: 0 4px 12px rgba(0,0,0,0.15); }

// 正确写法：使用 1px 细线形成清脆物理截断
.sticky-bar {
  border-bottom: 1px solid var(--tx-border-color);
  background-color: var(--tx-bg-color); // 必须是不透明实底
}
```

吸顶元素是视窗的物理闸门，而非空中悬浮物。1px 细线能明确界定滚出边界，保持整体版面的严谨秩序。

:::TuffDemoWrapper{demo="ConceptStickyHairlineDemo"}
:::
