1. 基础演示
- 定义一个滑轨:
<div id="slideway"></div>
- 给滑轨上色:
| #slideway { |
| background-color: #7ac23c; |
| height: 34px; |
| width: 0px; |
| transition: width 0.2s ease; // 过渡的css属性/执行时间/转速曲线(慢-快-慢) |
| } |
- 模拟拖动过程
| <script> |
| let w = 0; |
| let interval = setInterval(() => { |
| const slideway = document.querySelector("#slideway"); |
| slideway.style.width = `${w++}px`; |
| }, 50); |
| </script> |
- 动画演示

2. 增加滑块
- 定义一个滑块:
<div id="handler"></div>
- 给滑块上色:
| #handler { |
| position: absolute; |
| top: 0px; |
| left: 0px; |
| width: 40px; |
| height: 32px; |
| border: 1px solid #ccc; |
| cursor: move; |
| transition: left 0.2s ease; // 过渡的css属性/执行时间/转速曲线(慢-快-慢) |
| background: #fff |
| url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAALNJREFUWEft1sENgzAMBVBItuk+ZZIoZ66xMgndp+NEle+I2Ja/0oM5Qsh/fGQp+7b42hfnbwGIBv6/gdbamXO+Sinfu4khojffr7V+LBP12EDv/TXGuHjjlNJxhyAifs6Iw4KY/gI0Ygrgr0ciRAAkQgxAIVQABEIN8EaYAJ4IE8BzKtQAz3BuUgXwDlcBEOFiACpcBECGTwHo8CmAFyw9D1gOGNp3VGOo3VyyPgDRQDTwA8Qr1SGOkJt6AAAAAElFTkSuQmCC") |
| no-repeat center; |
| } |
- 模拟拖动过程
| <script> |
| let w = 0; |
| setInterval(() => { |
| const slideway = document.querySelector("#slideway"); |
| slideway.style.width = `${w++}px`; |
| const handler = document.querySelector("#handler"); |
| handler.style.left = `${w++}px`; |
| }, 50); |
| </script> |
- 动画演示

3. 基于Vue做拖动验证
状态分解
初始状态

拖动中状态

拖动完成状态

页面准备
| <div id="drag-verify"> |
| <div id="slideway"></div> |
| <div class="slideway-text" onselectstart="return false;" unselectable="on"> |
| {{ description }} |
| </div> |
| <div id="handler" class="handler handler-icon-init"></div> |
| </div> |
| <style scoped> |
| #drag-verify { |
| position: relative; |
| background-color: #e8e8e8; |
| width: 300px; |
| height: 34px; |
| line-height: 34px; |
| text-align: center; |
| } |
| #drag-verify .handler { |
| position: absolute; |
| top: 0px; |
| left: 0px; |
| width: 40px; |
| height: 32px; |
| border: 1px solid #ccc; |
| cursor: pointer; |
| transition: all 0.2s ease; |
| } |
| .handler-icon-init { |
| background: #fff |
| url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAALNJREFUWEft1sENgzAMBVBItuk+ZZIoZ66xMgndp+NEle+I2Ja/0oM5Qsh/fGQp+7b42hfnbwGIBv6/gdbamXO+Sinfu4khojffr7V+LBP12EDv/TXGuHjjlNJxhyAifs6Iw4KY/gI0Ygrgr0ciRAAkQgxAIVQABEIN8EaYAJ4IE8BzKtQAz3BuUgXwDlcBEOFiACpcBECGTwHo8CmAFyw9D1gOGNp3VGOo3VyyPgDRQDTwA8Qr1SGOkJt6AAAAAElFTkSuQmCC") |
| no-repeat center; |
| } |
| .handler-icon-pass { |
| background: #fff |
| url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAAAXNSR0IArs4c6QAAAnZJREFUWEfVl7Fr1HAUxz8vPWvBqeBiN930hpPL6eBU6FIFQcVWXOpSk02kjg7eLZ1cRFQudLC63Q2Cyw36D2ibDMJZxIJb6+YgUimYn9yZg1ySX5JLT0qz/vK+38/vvfd7+UU45EcO2Z+jB7DQYnL6DLNKmEFxSoSfStg1YKtZ5fOoGc2dAdtl3lfcFmERmEo0Eroo3pXg2XOT7TwwmQC2R8X3WRFhKY9g/x3FdzFYbVZ5mhWTCrC8waxh0AZOZglp1juOyZW0WC2A7XFOKboFjYfCHFPf7IkAgflH4MQ4ABDaTrXfO7EnEcBy6QDzBzL3aTDBTRTlQOehY7Ia1YwB2B5LSrF+UHPnAvV+JqEVQGz9mOJ8u8x+WDsJ4INSXCwM4NPomffiIwCIcKdZ5ZUWYNnFNGDzf5j3NAXeNE1uaAGsDeoYPCoEkLLzsF70RAyVwHJpAtYQQLyZ4nw5zYMhNePU2B2IRAHeAldDDnXHpBGtZRRQV/OkTPpQWzNx8wFk7SxrPYEgCyCxBIk7LGCeXQJdE0bN/rA4StpzN2HqMQxBDARTeyN57sZGcmwQWW7/UnE28SgWTXsgJmA3TZzUSWh5PEDxWDsLfBoyQSs0YvOOjR0UtfARDIZTPN5yeQ/MaZX/3XwGH5l8AAkl1ALYHnNK9SHG8wjd/WlqL0/zOyqovZBYHvdQPBkHgQhl3YU19UpmbbKA0CoMofjiH+PyWoVvOo3MS2kAcR+4NCKIs1di5XWFX2lxmQCD4Lsu1wVuIVxDcTxJVME2io4S1sPzfiwAA5F6l8mdvfiPSUnx9YXJpxGzdAR/zUbdYdb7uXsgS6jo+l8KUxAwNFONPgAAAABJRU5ErkJggg==") |
| no-repeat center; |
| } |
| #drag-verify #slideway { |
| background-color: #7ac23c; |
| height: 34px; |
| width: 0px; |
| transition: all 0.2s ease; |
| } |
| #drag-verify .slideway-text { |
| position: absolute; |
| top: 0px; |
| width: 300px; |
| -moz-user-select: none; |
| -webkit-user-select: none; |
| user-select: none; |
| -o-user-select: none; |
| -ms-user-select: none; |
| } |
| .unselect { |
| -moz-user-select: none; |
| -webkit-user-select: none; |
| -ms-user-select: none; |
| user-select: none; |
| } |
| .drag-verify-pass { |
| color: #fff; |
| } |
| </style> |
支持操作
- 验证成功监听
- 状态还原监听
- 状态还原函数
| this.dragVerify = new DragVerify( |
| () => { |
| this.$emit("onResult", { code: 200, message: "验证通过!" }); |
| }, |
| () => { |
| this.$emit("onResult", { code: 0, message: "未验证!" }); |
| } |
| ); |
| |
| // 验证后可以还原重新开始 |
| this.dragVerify.reset(); |
准备做事件绑定的Bus类:
| class Bus { |
| constructor() { |
| this.callbacks = {}; |
| } |
| |
| on(name, fn) { |
| this.callbacks[name] = this.callbacks[name] || []; |
| this.callbacks[name].push(fn); |
| } |
| |
| emit(name, args) { |
| if (this.callbacks[name]) { |
| this.callbacks[name].forEach((callback) => { |
| callback(args); |
| }); |
| } |
| } |
| } |
准备用于实际拖动验证的DragVerify类:
- 构造函数
- 获取dom元素为后续操作准备
- 实例化bus类来做事件通信
- 初始化触摸和鼠标事件
- 设置回调函数后将状态返回
| constructor(complete, reset) { |
| this.template = document.querySelector("#drag-verify"); |
| this.dragbg = document.querySelector("#slideway"); |
| this.handler = document.querySelector("#handler"); |
| this.bus = new Bus(); |
| this.initEvents(); |
| complete && this.bus.on("complete", complete); |
| reset && this.bus.on("reset", reset); |
| } |
- down事件处理
| down(e) { |
| this.diffX = 0; |
| this.cancelTransition(); |
| let clientX = 0; |
| if (e.type == "touchstart") { |
| clientX = e.changedTouches[0].clientX; |
| } else if (e.type == "mousedown") { |
| clientX = e.clientX; |
| } |
| this.diffX = clientX - this.handler.offsetLeft; |
| } |
- move事件处理
| move(e) { |
| |
| let clientX = 0; |
| if (e.type == "touchmove") { |
| clientX = e.changedTouches[0].clientX; |
| } else if (e.type == "mousemove") { |
| clientX = e.clientX; |
| } |
| let moveX = clientX - this.diffX; |
| if (moveX >= this.template.offsetWidth - this.handler.offsetWidth) { |
| moveX = this.template.offsetWidth - this.handler.offsetWidth; |
| } else if (moveX <= 0) { |
| moveX = 0; |
| } |
| |
| this.updateDistance(moveX); |
| } |
- up事件处理
| up(e) { |
| let clientX = 0; |
| if (e.type == "touchend") { |
| clientX = e.changedTouches[0].clientX; |
| document.ontouchmove = null; |
| document.ontouchend = null; |
| } else if (e.type == "mouseup") { |
| clientX = e.clientX; |
| document.onmousemove = null; |
| document.onmouseup = null; |
| } |
| |
| this.addTransition(); |
| if (clientX >= this.template.offsetWidth) { |
| this.complete(); |
| } else { |
| this.reset(); |
| } |
| } |
- 验证成功
| complete() { |
| this.template.className = "drag-verify-pass"; |
| this.handler.classList.add("handler-icon-pass"); |
| this.handler.onmousedown = null; |
| this.handler.ontouchstart = null; |
| this.bus.emit("complete"); |
| } |
- 恢复初始状态
| reset() { |
| this.template.className = "unselect"; |
| this.handler.classList.remove("handler-icon-pass"); |
| this.updateDistance(0); |
| this.initEvents(); |
| this.bus.emit("reset"); |
| } |
- 其他函数
| |
| updateDistance(x = 0) { |
| this.updateStyle([this.handler], "left", x + "px"); |
| this.updateStyle([this.dragbg], "width", x + "px"); |
| } |
| |
| |
| addTransition() { |
| this.template.className = ""; |
| this.updateStyle([this.handler, this.dragbg], "transition", "all .2s ease"); |
| } |
| |
| |
| cancelTransition() { |
| this.updateStyle([this.handler, this.dragbg], "transition", "none"); |
| } |
| |
| |
| updateStyle(selector, attr, content) { |
| selector.forEach((item) => { |
| item.style[attr] = content; |
| }); |
| } |
动画演示

完整代码drag-verify
参考项目Dreams-d/SliderTools
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)