前几天做一个 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 个高频坑
- 只做了滑块动画,没接业务状态
- 失败没有提示,只有回弹
- 阈值写死像素,换机型就飘
- 成功态不居中,细节观感差
- 页面返回不重置,状态穿透
可复用建议
如果你项目里不止一个登录入口,建议把它封装成组件:
- 入参:提示文案、阈值比例、失败停留时长
- 出参: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
// 调用登录接口
}