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; }); }
动画演示