返回博客

van-popup 关闭动画中内容闪空壳——用 after-leave 解耦才是正解

在一个微信小程序里,有一个帮助弹窗,内部根据 faqType 分多个子视图(wx:if 切换)。功能运行了很久没出问题,直到某次把弹窗内容扩展到更多分支后,真机测试时发现:点关闭按钮,弹窗在淡出的约 300ms 里会短暂闪出一个空白外壳。时间很短,但在 iPhone 上相当显眼,像白色方块一闪而过。

现象

  1. 点击关闭按钮,弹窗开始淡出动画
  2. 淡出过程中(约 150ms 左右)内容区域突然变空白,只剩半透明的弹窗外框
  3. 动画结束弹窗消失,不影响下次打开

模拟器上不明显,真机上(尤其 iPhone 12/14)很容易复现。

根因

van-popupshow prop 从 truefalse 时,触发的是 CSS transition 离开动画(vant-weapp 默认约 300ms)。这段动画期间:

  • popup 的 DOM 容器和遮罩仍然挂载在页面上,只是 opacity / transform 在过渡
  • setData同步应用到视图的

问题就出在 hideFaq 方法里同时重置了 faqVisible = falsefaqType = ''

// 反模式:两个变量同步清
hideFaq() {
  this.setData({
    faqVisible: false,
    faqType: '',  // 这一行是凶手
  });
}

faqType 变空后,popup 内部所有 wx:if="{{faqType === 'xxx'}}" 分支立即全部变 false,内容被卸载。而此时弹窗外壳还在动画淡出中,用户看到的就是"有外框但内容空了"的中间态。

坑:hide 里同步清内容条件 = 动画期间内容先消失

show 变 false 只是触发动画,不是立即卸载 DOM。动画的 300ms 内外壳还可见。如果内容条件变量和 show 同步清零,wx:if 立即把内容卸载,用户就会看到空壳在淡出。

setTimeout(300) 延迟清看似能绕开,但这是硬编码时长,vant 版本升级或基础库变更都会失效,快速连续 open/close 也会导致时序错乱。

正解

核心是三步分离:

  1. faqVisible(独立布尔)——show,只管开关,不混入任何业务语义
  2. faqType(内容条件变量)——wx:if 分支,与 show 完全解耦
  3. bind:after-leave——在 vant 内部 transition 结束后faqType
<van-popup
  show="{{faqVisible}}"
  round
  bind:close="hideFaq"
  bind:after-leave="onFaqAfterLeave"
>
  <view wx:if="{{faqType === 'order'}}">订单说明</view>
  <view wx:elif="{{faqType === 'score'}}">积分说明</view>
  <view wx:elif="{{faqType === 'coupon'}}">优惠券说明</view>
</van-popup>
Page({
  data: {
    faqVisible: false,
    faqType: '',
  },
 
  openFaq(e) {
    const type = e.currentTarget.dataset.type;
    // 先就位内容,再开动画——避免开场闪第一帧空壳
    this.setData({ faqType: type, faqVisible: true });
  },
 
  hideFaq() {
    // 只关 show,faqType 保持不动——动画期间内容继续显示
    this.setData({ faqVisible: false });
  },
 
  onFaqAfterLeave() {
    // transition 结束后 popup 已完全不可见,此时清内容条件无感知
    this.setData({ faqType: '' });
  },
});

数据流时序变成:

用户点关闭
  ↓ hideFaq() → faqVisible = false,faqType 不动
  ↓ van-popup 启动 CSS transition(300ms 淡出)
  ↓ 动画期间内容正常显示(用户看到完整内容淡出)
  ↓ transition 结束 → vant 派发 after-leave 事件
  ↓ onFaqAfterLeave() → faqType = ''(此时 popup 早已不可见)
正解:show 与内容条件双变量 + after-leave 事件清内容

bind:after-leave 是 vant 在 transition 真正结束时派发的事件,时机比硬编码 setTimeout 精确得多,且对 vant 版本升级自动兼容。把内容条件的重置放在这里,用户永远看不到"外壳有内容空了"的中间态。

van-dialogvan-action-sheet 等 vant 系列组件同样适用相同模式。

排查清单

出现 popup 关闭时白闪/空壳/内容切换闪烁时,逐项检查:

  • show 绑的是不是内容条件本身(如 show="{{!!faqType}}")?→ 解耦成独立布尔
  • hide 方法里是否同时清了 show 变量和内容条件变量?→ 只改 show,内容留到 after-leave
  • 是否挂了 bind:after-leave?→ 必须挂
  • open 时是否"先设内容再设 visible"?→ 避免开场第一帧空壳

一句话外卖

vant-weapp 的 show 关闭只是触发动画,不是立即销毁——内容条件必须等 after-leave 事件之后再清,不能跟 show 同步重置。