欢迎光临
我们一直在努力

uni-app—— uni-app 滚轮选择器惯性滚动导致弹框无法关闭的解决方案

问题背景

在小程序开发中,选择器(Picker)是非常常用的表单组件。用户通过滚动滚轮选择选项,然后点击"完成"确认选择。

然而在实际使用中,我们发现一个诡异的问题:用户快速滑动滚轮后立即点击"完成",弹框有时不会关闭,仍然遮挡在页面上。

问题复现

操作步骤:

  • 进入"新增数据"页面
  • 点击"一级分类"或"二级分类",弹出滚轮选择器
  • 快速滑动滚轮,在滚轮还在惯性滚动时
  • 立即点击"完成"按钮
  • 结果:弹框没有关闭,仍然遮挡在页面上
  • 关键点:如果等滚轮完全停止后再点击"完成",则一切正常。问题只出现在"滚轮还在动的时候点击完成"。

    问题分析

    根本原因:滚动惯性(Momentum Scrolling)导致的状态同步问题

    wd-picker 是基于滚轮的选择器组件,其内部实现依赖于滚动事件。当用户快速滑动并松手后,滚轮会继续惯性滚动一段时间,这就引发了一系列问题。

    1. 滚动惯性与事件竞态(Race Condition)

    用户操作时序:
    ┌─────────────────────────────────────────────────────────┐
    │ 手指滑动 → 松手 → 惯性滚动中 → 点击"完成" │
    │ ↑ ↑ │
    │ scrolling confirm 触发 │
    │ ↓ ↓ │
    │ scrollend 待触发 但状态未同步! │
    └─────────────────────────────────────────────────────────┘

    当用户在惯性动画未结束时点击"完成",会触发 confirm 事件与 scrollend 事件的竞态条件:

    • confirm 事件期望获取最终选中值
    • 但 scrollend 还没触发,选中值还在变化中
    • 导致内部状态未同步完成

    2. 值锁定机制缺陷

    滚轮选择器的工作原理:

    // 滚轮选择器内部逻辑(伪代码)
    class WheelPicker {
    scrolling = false
    pendingValue = null
    confirmedValue = null

    onScrollStart() {
    this.scrolling = true
    // 进入「值待定」状态
    }

    onScrollEnd() {
    this.scrolling = false
    this.pendingValue = this.calculateCurrentValue()
    // 滚动停止后才能锁定值
    }

    onConfirm() {
    if (this.scrolling) {
    // 问题:滚动中确认,值还没锁定!
    // 组件可能忽略此次确认,或产生异常行为
    return
    }
    this.confirmedValue = this.pendingValue
    this.closePopup()
    }
    }

    惯性滚动期间,组件处于「值待定」状态,此时触发确认操作会被忽略或产生异常行为。

    3. 弹窗关闭依赖链断裂

    弹窗关闭的正常流程:

    confirm 事件触发 → 获取选中值 → 触发回调 → 关闭弹窗
    ↓
    (scrolling=true 时)
    ↓
    内部状态异常 → 跳过关闭流程 → 弹窗不关闭!

    解决方案

    方案对比

    方案思路可行性
    方案A 在 confirm 时强制等待 scrollend 需要修改组件源码,复杂
    方案B 禁用惯性滚动 影响用户体验
    方案C 换用点击式选择器 从根本上避免问题

    最终方案:使用 wd-select-picker 替代 wd-picker

    <!– 修复前:使用滚轮选择器 –>
    <wd-picker
    v-model="subjectLevel1"
    :columns="level1Columns"
    label="一级科目"
    @confirm="onLevel1Confirm"
    />

    <!– 修复后:使用点击式选择器 –>
    <wd-select-picker
    v-model="subjectLevel1"
    :columns="level1Options"
    label="一级科目"
    @confirm="onLevel1Confirm"
    />

    数据结构调整

    // 修复前:wd-picker 使用的二维数组结构
    const level1Columns = ref([
    [
    { label: '人员经费', value: '1' },
    { label: '公用经费', value: '2' },
    { label: '专项经费', value: '3' },
    ]
    ])

    // 修复后:wd-select-picker 使用的一维数组结构
    const level1Options = ref([
    { label: '人员经费', value: '1' },
    { label: '公用经费', value: '2' },
    { label: '专项经费', value: '3' },
    ])

    为什么 wd-select-picker 能解决问题?

    wd-select-picker 是基于点击的列表选择器:

    特性wd-picker(滚轮)wd-select-picker(点击)
    交互方式 滚动选择 点击选择
    惯性滚动 有 无
    状态变更 异步(依赖 scrollend) 同步(点击即确定)
    竞态条件 存在 不存在

    点击式选择器采用「点击即选中」的交互模式:

    • 用户点击某个选项,该选项立即被选中
    • 状态变更是同步且确定的
    • 从根本上避免了竞态条件

    修复前后对比

    修复前(存在竞态)

    用户快速滑动 → 惯性滚动中 → 点击完成
    ↓
    scrollend 未触发
    ↓
    confirm 获取到的值不确定
    ↓
    弹窗关闭逻辑被跳过 → 弹窗不关闭!

    修复后(状态同步)

    用户点击选项 → 立即选中 → 点击完成
    ↓
    值已确定(同步)
    ↓
    confirm 正常执行 → 弹窗正常关闭

    技术总结

    1. 滚轮选择器的惯性滚动是把双刃剑

    惯性滚动提升了滑动体验,但也引入了状态同步问题。在以下场景需要特别注意:

    • 用户可能在滚动未停止时触发其他操作
    • 依赖滚动停止后的状态做后续处理

    2. Race Condition 在 UI 交互中的表现

    竞态条件不仅存在于多线程编程中,在 UI 交互中同样常见:

    • 动画未完成时触发下一个操作
    • 异步请求未返回时用户重复点击
    • 滚动惯性与用户点击的时序冲突

    3. 「避免问题」优于「修补问题」

    面对复杂的组件内部 bug,有时候换一个组件比修补原组件更简单高效:

    • 修补原组件:需要深入理解内部实现,可能引入新问题
    • 换用替代组件:从根本上绕过问题,风险更低

    4. 组件选型时考虑交互特性

    选择 UI 组件时,不仅要看外观,还要考虑交互特性:

    场景推荐组件
    选项少(< 10个) 点击式选择器 / Radio
    选项多、需要快速滚动 滚轮选择器(但要处理惯性问题)
    对确定性要求高 点击式选择器

    关键词:滚轮选择器、惯性滚动、Race Condition、竞态条件、wd-picker、wd-select-picker、弹窗不关闭

    适用场景:小程序开发、移动端表单、Picker 组件选型、UI 交互问题排查

    赞(0)
    未经允许不得转载:171主机测评 » uni-app—— uni-app 滚轮选择器惯性滚动导致弹框无法关闭的解决方案
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址