踩过的坑:滑块“能滑”不等于“可用”

0 阅读3分钟

前几天做一个 uni-app 登录页,UI 很快就出来了:渐变背景、毛玻璃卡片、输入框、滑块、登录按钮。看起来挺完整,结果同事一句话把我问住了:> “这个滑块只是动画,还是登录前真的必须通过?”很多项目都卡在这里:交互看起来有了,但业务约束没有闭环。所以我把这个滑块验证当成一个小模块,认真做了一遍。今天把实现思路分享出来,拿去就能用。


目标很简单,但要做完整

这个滑块验证我定了 4 个目标:

  • 手指拖动时滑块跟手移动
  • 没滑到位要明确失败(红色失败态 + 提示语)
  • 滑到位后显示成功(对勾 + 成功文案)
  • 只有成功后登录按钮才可点击

核心不是“能拖”,而是:状态可感知 + 逻辑可约束。


先建模:把它当状态机,不当动画

我把验证过程拆成 3 个状态:

  • 默认态:请按住滑块拖动到最右侧
  • 失败态:验证失败,请重新拖动
  • 成功态:验证成功

对应几个关键字段:

  • verified:是否成功
  • slideError:是否显示失败态
  • sliderX:滑块位移
  • maxX:最大位移
  • dragging:是否正在拖

这样一来,模板渲染和业务判断都围绕状态走,不会出现“UI 一套、逻辑一套”的分裂。


交互实现:touch 三步走

1)touchstart

记录初始手指位置和初始滑块位置。

2)touchmove

计算位移并限制边界:0 <= sliderX <= maxX。

3)touchend

做阈值判断。我建议阈值按比例:threshold = maxX * 0.88,不要写死像素。

  • 到阈值:进入成功态
  • 不到阈值:进入失败态,1 秒后回到默认态

为什么失败态很重要?

很多实现里失败只是“回弹一下”,没有视觉反馈。用户会疑惑:我刚才是没拖够?还是触发有问题?所以我加了失败反馈:

  • 红色条背景
  • X 图标
  • 文案:验证失败,请重新拖动

这三件事会明显降低用户理解成本。


按钮联动:闭环的最后一步

登录按钮必须和验证状态绑定:

  • :disabled="!verified"
  • 点击登录再做一次兜底判断

这样可以确保:没通过滑块,绝对不能登录。这一步是“能用”和“好看”最关键的分界线。


我总结的 5 个高频坑

  1. 只做了滑块动画,没接业务状态
  1. 失败没有提示,只有回弹
  1. 阈值写死像素,换机型就飘
  1. 成功态不居中,细节观感差
  1. 页面返回不重置,状态穿透

可复用建议

如果你项目里不止一个登录入口,建议把它封装成组件:

  • 入参:提示文案、阈值比例、失败停留时长
  • 出参:success / reset
  • 外部统一控制登录按钮

后面你接短信、人机二次校验也会更顺。


最后

滑块验证最容易“看起来完成”,也最容易“实际上没完成”。把它当成一个小状态机来设计,交互和业务才会真正闭环。如果你也在做 uni-app 登录模块,希望这套思路能帮你少踩几个坑。


版本 B:代码片段版(技术味更重)

核心状态

data() {

  return {

    verified: false,

    slideError: false,

    sliderX: 0,

    maxX: 0,

    dragging: false,

    startX: 0,

    startSliderX: 0

  }

}

核心交互

onSlideTouchStart(e) {

  if (this.verified) return

  this.dragging = true

  this.startX = e.touches[0].clientX

  this.startSliderX = this.sliderX || 0

},

onSlideTouchMove(e) {

  if (!this.dragging) return

  const curX = e.touches[0].clientX

  const dx = curX - this.startX

  let next = this.startSliderX + dx

  next = Math.max(0, Math.min(this.maxX, next))

  this.sliderX = next

},

onSlideTouchEnd() {

  if (!this.dragging || this.verified) return

  this.dragging = false

  const threshold = this.maxX * 0.88

  if (this.sliderX >= threshold && this.maxX > 0) {

    this.sliderX = this.maxX

    setTimeout(() => { this.verified = true }, 120)

  } else {

    this.slideError = true

    this.sliderX = 0

    setTimeout(() => { this.slideError = false }, 1000)

  }

}

登录约束

<button :disabled="!verified" @click="handleLogin">登录

handleLogin() {

  if (!this.verified) return

  // 调用登录接口

}