原生JavaScript實(shí)現(xiàn)滑動(dòng)拖動(dòng)驗(yàn)證的示例代碼
本文介紹了原生JavaScript實(shí)現(xiàn)滑動(dòng)拖動(dòng)驗(yàn)證的示例代碼,分享給大家,具體如下:

通常,我們?yōu)榱朔乐褂脩魫阂馓峤槐韱?,?huì)讓用戶在提交前完成滑動(dòng)拖動(dòng)驗(yàn)證,有時(shí)候這也能起到一絲反爬的作用。
實(shí)現(xiàn)滑動(dòng)驗(yàn)證的方式當(dāng)然不止一種,這里我們直接使用原生 JavaScript 來(lái)實(shí)現(xiàn)。
現(xiàn)在,你可以在這里 看到完整的源碼。
原生實(shí)現(xiàn)
原生 JavaScript 的實(shí)現(xiàn),主要是通過(guò)監(jiān)聽(tīng)鼠標(biāo)事件來(lái)對(duì) DOM 進(jìn)行一系列的操作。
滑塊驗(yàn)證的結(jié)構(gòu)主要分為四個(gè)部分:軌道、滑塊、背景和文案,我們可以使用下面的 HTML 結(jié)構(gòu)來(lái)表示。
<div class="slide-track"> <div class="slide-bg"></div> <div class="slide-block"></div> <p class="slide-text">請(qǐng)按住滑塊,拖動(dòng)到最右邊</p> </div>
基本思路就是我們給滑塊(.slide-block)添加相應(yīng)的事件,在按下滑塊時(shí)記錄鼠標(biāo)的當(dāng)前位置并添加滑動(dòng)事件,在滑動(dòng)過(guò)程中根據(jù)鼠標(biāo)的移動(dòng)來(lái)移動(dòng)滑塊的位置和增加背景元素(.slide-bg)的寬度,直到移動(dòng)到軌道(.slide-track)的末端后,改變文案(.slide-text)來(lái)提示成功。
樣式
在開(kāi)始寫腳本之前可以先來(lái)完成一下它們的樣式,這讓滑塊相關(guān)的部分看起來(lái)更好,也讓后面的工作更愉快的進(jìn)行。
/* 樣式的注意事項(xiàng) */
樣式的寫法就不貼了,相信大家一看就懂,而且會(huì)有更好的實(shí)現(xiàn)。需要的話,也可以在 Github 上找到。
腳本
現(xiàn)在開(kāi)始來(lái)實(shí)現(xiàn)腳本的內(nèi)容,首先我們對(duì) document.querySelector 方法進(jìn)行簡(jiǎn)單的封裝以方便后續(xù)操作 DOM 元素。
function $(selectors) {
return document.querySelector(selectors);
}
然后通過(guò)自定義的 _h 函數(shù)我們可以很方便的創(chuàng)建上面的 HTML 結(jié)構(gòu),并添加到文檔中。
function _h(tagName, propMap = {}, text) {
const ele = document.createElement(tagName);
Object.keys(propMap).forEach(prop => ele.setAttribute(prop, propMap[prop]));
if (text) {
ele.appendChild(document.createTextNode(text));
}
return ele;
}
class SlideUnlock {
constructor(el = "body", options = {}) {
this.$el = $(el)
this.$$isSuccess = false
this.$options = { // 默認(rèn)配置
tip: '請(qǐng)按住滑塊,拖動(dòng)到最右邊',
unlockText: '驗(yàn)證成功',
duration: 500,
...options
}
}
init() {
this.$$root = _h("div", { class: "slide-track" }) // 軌道
this.$$bg = this.$$root.appendChild(_h("div", { class: "slide-bg" }))
this.$$block = this.$$root.appendChild(
_h("div", { class: "slide-block" }) // 滑塊
)
this.$$text = this.$$root.appendChild(
_h("p", { class: "slide-text" }, this.$options.tip)
)
this.$el.insertBefore(this.$$root, this.$el.firstChild)
}
}
在創(chuàng)建好 DOM 結(jié)構(gòu)后,接下來(lái)為滑塊添加鼠標(biāo)按下的事件,在這個(gè)事件中我們需要記錄下鼠標(biāo)的初始橫坐標(biāo),以便后續(xù)和滑動(dòng)過(guò)程中的位置相比較,同時(shí)為其添加滑動(dòng)事件。
class SlideUnlock {
init() {
/* ... */
this.$$block.addEventListener(
"mousedown",
(this.$$handleMouseDown = this._handleMouseDown.bind(this)),
false
)
}
_handleMouseDown(e) {
const downx = e.clientX
e.target.addEventListener(
"mousemove",
(this.$$handleMouseMove = this._handleMouseMove.bind(this, downx)),
false
)
e.preventDefault()
}
_handleMouseMove(downx, e) {}
}
在這里有點(diǎn)細(xì)節(jié)需要注意:
- 首先,由于事件監(jiān)聽(tīng)器中的
this指向的是觸發(fā)事件的元素,為此我們?cè)谥付ㄊ髽?biāo)按下的監(jiān)聽(tīng)器時(shí)為其綁定了this,以便調(diào)用滑塊實(shí)例屬性和原型上的方法。 - 其次,我們?cè)谑髽?biāo)按下的監(jiān)聽(tīng)器中添加了鼠標(biāo)移動(dòng)的監(jiān)聽(tīng)器,如果在初始時(shí)同按下的監(jiān)聽(tīng)器一起指定,那么會(huì)先執(zhí)行鼠標(biāo)移動(dòng)的監(jiān)聽(tīng)器,而此時(shí)并沒(méi)有記錄鼠標(biāo)的初始位置;
接下來(lái)我們要實(shí)現(xiàn)滑動(dòng)過(guò)程中的主要邏輯:根據(jù)鼠標(biāo)的移動(dòng)實(shí)時(shí)地更新滑塊的位置,并對(duì)一些臨界位置進(jìn)行處理。
_handleMouseMove(downx, e) {
const info = this.$$block.getBoundingClientRect(),
x = e.clientX,
y = e.clientY,
x1 = info.left,
y1 = info.top,
x2 = info.right,
y2 = info.bottom,
moveX = x - downx
if (this.$$isSuccess) {
return
}
if (moveX < 0) {
return
}
if (x < x1 || x > x2 || y < y1 || y > y2) {
// 當(dāng)鼠標(biāo)移開(kāi)滑塊時(shí)取消移動(dòng)
return
}
this.$$block.style.left = `${moveX}px` // 更新滑塊的我i之
this.$$bg.style.width = `${moveX}px` // 同步增大背景元素的寬度
// 當(dāng)滑塊滑動(dòng)的距離大于等于軌道除去滑塊寬度后的距離時(shí)表示已經(jīng)到達(dá)軌道的最右邊了
if (moveX >= this.$$root.offsetWidth - (x2 - x1)) {
this.$$isSuccess = true
this.$$text.textContent = "驗(yàn)證成功"
this.$$text.style.cssText = `color: #fff; left: 0; right: ${this.$$block.offsetWidth}px`
this.$$block.classList.add("success")
}
}
這里的實(shí)現(xiàn)也很簡(jiǎn)單,唯一需要看一下的就是通過(guò) getBoundingClientRect 來(lái)獲取了滑塊相對(duì)于視口的位置,然后根據(jù)鼠標(biāo)所在的位置來(lái)判斷鼠標(biāo)是否在滑塊上,如果不在則取消移動(dòng)。
現(xiàn)在它已經(jīng)能很好的滑動(dòng),并完成提示成功的基本功能了。但是,當(dāng)我們每次滑動(dòng)到中間就取消,然后再次點(diǎn)擊滑動(dòng)時(shí),就會(huì)導(dǎo)致重復(fù)的添加滑動(dòng)事件,而且中途釋放后,滑塊就停在了當(dāng)前位置,這顯然不對(duì)。
解決的辦法就是在添加鼠標(biāo)按下事件的時(shí)候,同時(shí)也指定一個(gè)松開(kāi)的事件,在這個(gè)事件的監(jiān)聽(tīng)器中判斷如果沒(méi)有成功則取消之前綁定的滑動(dòng)事件,并進(jìn)行重置,為了看起來(lái)更友好,我們還可以加上一點(diǎn)動(dòng)畫。
class SlideUnlock {
init() {
/* ... */
document.addEventListener(
"mouseup",
(this.$$handleMouseUp = this._handleMouseUp.bind(this)),
false
)
}
_handleMouseDown(e) {
/* ... */
// 取消在手動(dòng)滑動(dòng)過(guò)程中的動(dòng)畫效果
this.$$bg.style.transition = ""
this.$$block.style.transition = ""
/* ... */
}
_handleMouseUp(e) {
this.$$block.removeEventListener(
"mousemove",
this.$$handleMouseMove,
false
)
if (this.$$isSuccess) {
return
}
// 給重置過(guò)程添加動(dòng)畫效果
this.$$bg.style.transition = "width 1s ease"
this.$$block.style.transition = "left 1s ease"
this.$$block.style.left = 0
this.$$bg.style.width = 0
}
}
目前為止,滑塊已經(jīng)可以在 PC 端正常的工作了,不過(guò)在移動(dòng)端卻并不理想。
為了它能夠在移動(dòng)端也可以很好的工作,我們可以借助 touchstart、touchmove、touchend 等事件來(lái)完成。
綁定這些事件的時(shí)機(jī)和處理方式和之前的三個(gè)事件分別相對(duì)應(yīng),所以我們新增兩個(gè)方法(和 jQuery 的 on、off 方法很像)來(lái)更好的添加和移除事件。
function bindEvents(events, handler, element = $("body")) {
events.split(" ").forEach(event => {
element.addEventListener(event, handler, false)
})
}
function unbindEvents(events, handler, element = $("body")) {
events.split(" ").forEach(event => {
element.removeEventListener(event, handler, false)
})
}
根據(jù)這兩個(gè)方法,我們來(lái)稍微修改一下滑塊中添加事件的代碼。
function bindEvents(events, handler, element = $("body")) {
events.split(" ").forEach(event => {
element.addEventListener(event, handler, false)
})
}
function unbindEvents(events, handler, element = $("body")) {
events.split(" ").forEach(event => {
element.removeEventListener(event, handler, false)
})
}
另外,需要注意的是在移動(dòng)端 touch 事件中獲取 clientX、clientY 時(shí)不能在事件對(duì)象上直接讀取,而是在 event.changedTouches[0] 對(duì)象上取得。
現(xiàn)在,它已經(jīng)能夠同時(shí)在 PC 端和移動(dòng)端上工作了,不過(guò)我們還能對(duì)它進(jìn)行一些優(yōu)化,比如使用函數(shù)節(jié)流。
函數(shù)節(jié)流的實(shí)現(xiàn)方式有很多,這里列一下我們?cè)诒敬芜^(guò)程中使用的方式。
utils.throttle = function(method, context = {}, delay = 4, ...outParams) {
return function(...innerParams) {
clearTimeout(context.$$tId)
context.$$tId = setTimeout(function() {
method.apply(context, [...outParams, ...innerParams])
}, delay)
}
}
然后用這個(gè)節(jié)流函數(shù),來(lái)包裝我們移動(dòng)時(shí)的處理函數(shù),并根據(jù)實(shí)際情況做點(diǎn)調(diào)整。
除此之外,我們還可以添加一個(gè)重置的方法,讓它回到最初的狀態(tài),涉及到的內(nèi)容也很簡(jiǎn)單,就是在成功的狀態(tài)下重新設(shè)置樣式和綁定事件。
reset() {
if (!this.$$isSuccess) {
return
}
this.$$isSuccess = false
this.$$bg.style.cssText =
`transition: width ${this.$options.duration}ms ease; width: 0;`
this.$$block.style.cssText =
`transition: left ${this.$options.duration}ms ease; left: 0;`
this.$$text.style.cssText =
`color: #5f5f5f; left: ${this.$$block.offsetWidth}px; right: 0;`
this.$$text.textContent = this.$options.tip
this.$$block.classList.remove("success")
this._bindEvents()
}
好了,滑塊的實(shí)現(xiàn)到這里就告一段落了,相信大家看到這里已經(jīng)完全明白了,甚至有更好的實(shí)現(xiàn)。
如何使用
你可以簡(jiǎn)單的在 HTML 頁(yè)面中引入該腳本,然后根據(jù)自己的需求設(shè)置合適的樣式;不過(guò)更好的方式是通過(guò)這樣的思路,在項(xiàng)目中做一些改進(jìn)(比如平滑降級(jí))等處理。
接下來(lái)是一個(gè)簡(jiǎn)單的使用模板。
<body> <script src="slide-unlock/core.js"></script> <script> const slider = new SlideUnlock() slider.init() </script> </body>
你可以在 這里 看見(jiàn)完整的使用方式和效果。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
vue 自定義指令directive的使用場(chǎng)景
這篇文章主要詳細(xì)介紹了vue 自定義指令directive的使用場(chǎng)景,文中有詳細(xì)的代碼示例,感興趣的小伙伴歡迎閱讀2023-04-04
不到200行 JavaScript 代碼實(shí)現(xiàn)富文本編輯器的方法
這篇文章主要介紹了不到200行 JavaScript 代碼實(shí)現(xiàn)富文本編輯器的方法,需要的朋友可以參考下2018-01-01
學(xué)習(xí)JavaScript圖片預(yù)加載模塊
這篇文章主要介紹了js實(shí)現(xiàn)圖片預(yù)加載的方法,內(nèi)容很詳細(xì),帶領(lǐng)大家全面認(rèn)識(shí)js圖片預(yù)加載模塊,感興趣的小伙伴們可以參考一下2016-11-11
JavaScript通過(guò)改變文字透明度實(shí)現(xiàn)的文字閃爍效果實(shí)例
這篇文章主要介紹了JavaScript通過(guò)改變文字透明度實(shí)現(xiàn)的文字閃爍效果,結(jié)合完整實(shí)例形式分析了javascript基于定時(shí)器周期性動(dòng)態(tài)修改頁(yè)面元素屬性的相關(guān)操作技巧,需要的朋友可以參考下2017-04-04
關(guān)于js二維數(shù)組和多維數(shù)組的定義聲明(詳解)
下面小編就為大家?guī)?lái)一篇關(guān)于js二維數(shù)組和多維數(shù)組的定義聲明(詳解)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-10-10
利用JavaScript腳本實(shí)現(xiàn)滾屏效果的方法
這篇文章主要介紹了利用JavaScript腳本實(shí)現(xiàn)滾屏效果的方法,即一個(gè)公告欄顯示般的效果,需要的朋友可以參考下2015-07-07
JS實(shí)現(xiàn)最簡(jiǎn)單的冒泡排序算法
這篇文章主要介紹了JS實(shí)現(xiàn)最簡(jiǎn)單的冒泡排序算法,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-02-02

