121 lines
4.6 KiB
JavaScript
121 lines
4.6 KiB
JavaScript
/**
|
|
* slider.js - 滑块验证组件(纯前端实现)
|
|
* 用法:
|
|
* <div id="slider"></div>
|
|
* var slider = new SliderVerify('#slider', function(token){ ... }); // 拖动成功回调
|
|
* slider.reset(); // 重置
|
|
* 说明:拖动到目标位置后生成 slider_token(含随机数+偏移校验和),
|
|
* 提交登录时携带该 token;服务端校验 token 非空且长度合法。
|
|
* 如需更高安全性,可后续改为服务端签发 token(接口生成背景图+缺口位置)。
|
|
*/
|
|
(function (window) {
|
|
'use strict';
|
|
|
|
function SliderVerify(selector, onSuccess) {
|
|
this.el = typeof selector === 'string' ? document.querySelector(selector) : selector;
|
|
this.onSuccess = onSuccess || function () {};
|
|
this.passed = false;
|
|
this.init();
|
|
}
|
|
|
|
SliderVerify.prototype.init = function () {
|
|
var self = this;
|
|
var html =
|
|
'<div class="slider-box">' +
|
|
' <div class="slider-bg"></div>' +
|
|
' <div class="slider-text">请按住滑块,拖动到最右边</div>' +
|
|
' <div class="slider-btn">⟶</div>' +
|
|
'</div>';
|
|
this.el.innerHTML = html;
|
|
|
|
this.box = this.el.querySelector('.slider-box');
|
|
this.bg = this.el.querySelector('.slider-bg');
|
|
this.text = this.el.querySelector('.slider-text');
|
|
this.btn = this.el.querySelector('.slider-btn');
|
|
|
|
this.boxWidth = this.box.offsetWidth || 300;
|
|
this.dragging = false;
|
|
this.startX = 0;
|
|
this.offset = 0;
|
|
|
|
this.btn.addEventListener('mousedown', function (e) { self.startDrag(e); });
|
|
this.btn.addEventListener('touchstart', function (e) { self.startDrag(e.touches[0]); }, { passive: true });
|
|
|
|
document.addEventListener('mousemove', function (e) { self.moveDrag(e); });
|
|
document.addEventListener('touchmove', function (e) { self.moveDrag(e.touches[0]); }, { passive: false });
|
|
document.addEventListener('mouseup', function () { self.endDrag(); });
|
|
document.addEventListener('touchend', function () { self.endDrag(); });
|
|
};
|
|
|
|
SliderVerify.prototype.startDrag = function (e) {
|
|
if (this.passed) return;
|
|
e.preventDefault && e.preventDefault();
|
|
this.dragging = true;
|
|
this.startX = e.clientX;
|
|
};
|
|
|
|
SliderVerify.prototype.moveDrag = function (e) {
|
|
if (!this.dragging) return;
|
|
e.preventDefault && e.preventDefault();
|
|
var dx = e.clientX - this.startX;
|
|
this.offset = Math.max(0, Math.min(dx, this.boxWidth - 50));
|
|
this.btn.style.left = this.offset + 'px';
|
|
this.bg.style.width = this.offset + 50 + 'px';
|
|
if (this.offset >= this.boxWidth - 60) {
|
|
this.endDrag(true);
|
|
}
|
|
};
|
|
|
|
SliderVerify.prototype.endDrag = function (force) {
|
|
if (!this.dragging) return;
|
|
this.dragging = false;
|
|
|
|
if (force || this.offset >= this.boxWidth - 60) {
|
|
// 验证通过
|
|
this.passed = true;
|
|
this.btn.style.left = (this.boxWidth - 50) + 'px';
|
|
this.bg.style.width = '100%';
|
|
this.text.innerHTML = '✓ 验证通过';
|
|
this.text.className = 'slider-text slider-success';
|
|
|
|
var token = this.makeToken();
|
|
if (this.onSuccess) this.onSuccess(token);
|
|
} else {
|
|
// 复位
|
|
var self = this;
|
|
this.btn.style.transition = 'left .3s';
|
|
this.bg.style.transition = 'width .3s';
|
|
this.btn.style.left = '0px';
|
|
this.bg.style.width = '50px';
|
|
setTimeout(function () {
|
|
self.btn.style.transition = '';
|
|
self.bg.style.transition = '';
|
|
}, 320);
|
|
}
|
|
};
|
|
|
|
/** 生成滑块 token:时间戳+随机数+偏移校验和 */
|
|
SliderVerify.prototype.makeToken = function () {
|
|
var rand = Math.random().toString(36).substr(2, 8);
|
|
var ts = Date.now().toString(36);
|
|
var offset = this.offset;
|
|
var checksum = (offset * 7 + ts.length * 13).toString(36);
|
|
return 'sl_' + ts + '_' + rand + '_' + checksum + '_' + offset;
|
|
};
|
|
|
|
SliderVerify.prototype.reset = function () {
|
|
this.passed = false;
|
|
this.offset = 0;
|
|
this.btn.style.left = '0px';
|
|
this.bg.style.width = '50px';
|
|
this.text.innerHTML = '请按住滑块,拖动到最右边';
|
|
this.text.className = 'slider-text';
|
|
};
|
|
|
|
SliderVerify.prototype.isPassed = function () {
|
|
return this.passed;
|
|
};
|
|
|
|
window.SliderVerify = SliderVerify;
|
|
})(window);
|