Post

一个小细节,让分类标签栏变得更优雅

index-tabs-expand-fade 渐变遮罩设计拆解

一个小细节,让分类标签栏变得更优雅

🎬 从一个”丑”说起

如果你打开过各种内容类 App 或小程序,你一定见过这种场景:页面顶部有一排可以左右滑动的分类标签栏,比如”关注”、”全部”、”美食”、”房产”、”招聘”等。标签太多一行放不下,右边就会有一个小箭头按钮 ▼,点一下可以展开看到所有分类。

问题来了——这个箭头按钮是固定在最右边的,如果标签文字刚好被按钮“一刀切”,效果就会非常生硬:文字到一半突然消失了,就像被刀砍掉了尾巴。

index-tabs-expand-fade 就是为了解决这个问题。它在”可滑动的标签区域”和”右侧固定按钮”之间,插入了一段透明到白色的渐变过渡带,让被遮住的标签文字像是自然地”融化消散”进白色背景里,而不是被生硬地截断。


🧱 三层”三明治”结构

这个效果的秘密非常简单,用了三层叠加。就像做三明治一样,从上到下分别是:

层级元素z-index说明
🔝 最上层展开按钮 ▼12可点击,白色背景,宽 28px
🔵 中间层渐变遮罩11不可点击,透明→白色渐变,宽 30px
⬜ 底层标签滑动区域默认可左右滚动的 t-tabs 组件

底层是那些可以自由左右滑动的分类标签;中间叠了一个透明→白色的渐变色条(就是主角 index-tabs-expand-fade);最上面是那个带下箭头的展开按钮。


🔍 代码拆解

① WXML 结构:一行代码搞定

渐变遮罩在模板中只需要一个空的 <view>,没有任何子元素,所有视觉效果全靠 CSS:

1
2
3
4
5
6
7
8
9
10
11
12
13
<!-- 渐变遮罩(独立于按钮,不拦截点击) -->
<view
  wx:if="{{ '{{' }}!cateTabsExpanded && !catePanelClosing{{ '}}' }}"
  class="index-tabs-expand-fade"
  ></view>

<!-- 展开/收起按钮(仅箭头区域可点击) -->
<view class="index-tabs-expand" catchtap="onExpandCateTabs">
  <text class="iconfont icon-unfold"
    style="transition: transform 0.3s ease;
    transform: rotate({{ '{{' }}cateTabsExpanded ? '180deg' : '0deg'{{ '}}' }});"
    />
</view>

注意 wx:if 条件:只有当面板处于收起状态时渐变才显示。一旦用户点击展开,面板展开成网格模式,横向滑动区域消失了,渐变自然也不需要了。

② CSS 样式:渐变魔法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 渐变遮罩(独立于按钮,不拦截点击) */
.index-tabs-expand-fade {
  position: absolute;
  right: 28px;          /* 紧贴按钮左边 */
  top: 0;
  width: 30px;          /* 渐变过渡带宽度 */
  height: 76px;         /* 与标签栏等高 */
  background: linear-gradient(
    to right,
    rgba(255,255,255,0) 0%,      /* 左侧:完全透明 */
    rgba(255,255,255,0.5) 35%,   /* 中间:半透明白 */
    #fff 100%                     /* 右侧:纯白色 */
  );
  pointer-events: none;  /* ★ 不拦截任何触摸 */
  z-index: 11;           /* 在标签上面,在按钮下面 */
}

核心就是 linear-gradient 这行,它定义了一个从左到右的颜色渐变:

  • 左边起点(0%)rgba(255,255,255,0) —— 完全透明,底下标签文字完全可见,不会有突兀的分界线
  • 中间过程(35%)rgba(255,255,255,0.5) —— 在约第 10px 处透明度已到 50%,文字开始明显变淡,给人”正在消散”的暗示
  • 右边终点(100%)#fff —— 纯白色,与展开按钮的白色背景完美融合,没有任何色差

💡 pointer-events: none 是关键中的关键 —— 这个属性让遮罩对手指触摸完全透明。你在这个区域滑动时,手指穿透遮罩直接触达底下的标签,滑动体验完全不受影响。

③ JS 逻辑:展开 / 收起联动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// 页面数据
data: {
  cateTabsExpanded: false,  // 面板是否展开
  catePanelClosing: false,  // 收起动画是否进行中
}

// 点击展开/收起按钮
onExpandCateTabs: function () {
  var expanding = !this.data.cateTabsExpanded;

  if (expanding) {
    // → 展开:渐变遮罩立即消失(wx:if 变 false)
    this.setData({
      cateTabsExpanded: true,
      catePanelClosing: false
    });
  } else {
    // → 收起:先播放收起动画,动画结束后渐变遮罩才重新出现
    this._closeCatePanel();
  }
}

// 带动画的收起逻辑
_closeCatePanel: function () {
  var that = this;
  // 第一步:触发 CSS 收起动画
  this.setData({
    catePanelClosing: true,
    cateTabsExpanded: false
  });

  // 第二步:260ms 后动画结束,渐变遮罩重新出现
  setTimeout(function () {
    that.setData({ catePanelClosing: false });
  }, 260);
}

🧠 为什么要两步走?

如果面板收起动画还没播完就立刻让渐变出现,用户会看到渐变色条和网格面板同时存在的混乱画面。所以用了一个 catePanelClosing 状态变量当”缓冲带”——动画播放期间(260ms),渐变遮罩保持隐藏;等动画播完了,才优雅地让渐变重新出场。


🎯 为什么不直接在按钮上加渐变?

你可能会想:直接给展开按钮加个渐变不就行了?何必单独弄一个独立元素?

方案优点问题
按钮上加渐变代码更少按钮的 catchtap 会拦截渐变区域的触摸→ 用户滑动标签到右侧时手感”卡住”
独立渐变元素 (当前方案)pointer-events: none 触摸穿透,不影响滑动多一个 DOM 元素

核心取舍在于触摸穿透。如果渐变是按钮的一部分,当用户手指滑到右侧区域继续滑动标签时,会被按钮的 catchtap 吃掉事件导致”滑不动”。而独立元素 + pointer-events: none 的组合,让渐变只管”看”,不管”摸”——把视觉层交互层完美分离。


🔄 展开面板的完整联动流程

渐变遮罩不是单独存在的效果,它是展开/收起整套交互中的一环:

  1. 收起状态(默认) → 横向可滑动的标签栏可见,右侧叠加 30px 渐变遮罩 + 28px 展开按钮(▼)。用户可以自由左右滑动浏览所有分类。
  2. 点击 ▼ 展开 → t-tabs 被 hidden 隐藏(不是销毁,滚动位置保持不变),网格面板以动画滑出,显示所有分类。渐变遮罩消失,箭头旋转 180° 变成 ▲。背后弹出半透明黑色遮罩覆盖整个页面。
  3. 点击分类项 / 点击遮罩 / 点击 ▲ → 触发收起动画(耗时 250ms),网格面板向上缩小消失。
  4. 动画结束后(260ms) → catePanelClosing 重置为 false,渐变遮罩重新出现,回到收起状态。标签栏的滚动位置完好如初(因为用的是 hidden 而非 wx:if)。

✨ 总结

index-tabs-expand-fade 本质上就是一个 30px 宽的渐变色条,但它体现了几个有意思的前端细节思考:

  1. 渐变而非切割 —— 用 linear-gradient 实现自然的淡出效果,比 overflow: hidden 截断柔和得多
  2. 触摸穿透 —— pointer-events: none 让遮罩”只看不摸”,视觉上有东西在那里,但手指可以穿透操作底下的标签
  3. 状态驱动显隐 —— 通过 cateTabsExpandedcatePanelClosing 两个布尔值精确控制遮罩出现的时机,避免动画期间的视觉冲突
  4. 独立分层 —— 渐变遮罩、展开按钮、标签栏各自独立,用 z-index 控制层叠关系,职责清晰互不干扰

看起来只是一个不起眼的 30 像素,但用户在滑动标签栏的时候,是能”感觉到”这种流畅的。好的用户体验,往往就是由这些看不见的小心思累积起来的。

This post is licensed under CC BY 4.0 by the author.