原生JavaScript实现滑动拖动验证的示例代码


Posted in Javascript onDecember 06, 2019

本文介绍了原生JavaScript实现滑动拖动验证的示例代码,分享给大家,具体如下:

原生JavaScript实现滑动拖动验证的示例代码

通常,我们为了防止用户恶意提交表单,会让用户在提交前完成滑动拖动验证,有时候这也能起到一丝反爬的作用。

实现滑动验证的方式当然不止一种,这里我们直接使用原生 JavaScript 来实现。

现在,你可以在这里 看到完整的源码。

原生实现

原生 JavaScript 的实现,主要是通过监听鼠标事件来对 DOM 进行一系列的操作。

滑块验证的结构主要分为四个部分:轨道、滑块、背景和文案,我们可以使用下面的 HTML 结构来表示。

<div class="slide-track">
 <div class="slide-bg"></div>
 <div class="slide-block"></div>
 <p class="slide-text">请按住滑块,拖动到最右边</p>
</div>

基本思路就是我们给滑块(.slide-block)添加相应的事件,在按下滑块时记录鼠标的当前位置并添加滑动事件,在滑动过程中根据鼠标的移动来移动滑块的位置和增加背景元素(.slide-bg)的宽度,直到移动到轨道(.slide-track)的末端后,改变文案(.slide-text)来提示成功。

样式

在开始写脚本之前可以先来完成一下它们的样式,这让滑块相关的部分看起来更好,也让后面的工作更愉快的进行。

/* 样式的注意事项 */

样式的写法就不贴了,相信大家一看就懂,而且会有更好的实现。需要的话,也可以在 Github 上找到。

脚本

现在开始来实现脚本的内容,首先我们对 document.querySelector 方法进行简单的封装以方便后续操作 DOM 元素。

function $(selectors) {
 return document.querySelector(selectors);
}

然后通过自定义的 _h 函数我们可以很方便的创建上面的 HTML 结构,并添加到文档中。

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 = { // 默认配置
   tip: '请按住滑块,拖动到最右边',
   unlockText: '验证成功',
   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)
 }
}

在创建好 DOM 结构后,接下来为滑块添加鼠标按下的事件,在这个事件中我们需要记录下鼠标的初始横坐标,以便后续和滑动过程中的位置相比较,同时为其添加滑动事件。

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) {}
}

在这里有点细节需要注意:

  • 首先,由于事件监听器中的 this 指向的是触发事件的元素,为此我们在指定鼠标按下的监听器时为其绑定了 this,以便调用滑块实例属性和原型上的方法。
  • 其次,我们在鼠标按下的监听器中添加了鼠标移动的监听器,如果在初始时同按下的监听器一起指定,那么会先执行鼠标移动的监听器,而此时并没有记录鼠标的初始位置;

接下来我们要实现滑动过程中的主要逻辑:根据鼠标的移动实时地更新滑块的位置,并对一些临界位置进行处理。

_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) {
  // 当鼠标移开滑块时取消移动
  return
 }

 this.$$block.style.left = `${moveX}px` // 更新滑块的我i之
 this.$$bg.style.width = `${moveX}px` // 同步增大背景元素的宽度

 // 当滑块滑动的距离大于等于轨道除去滑块宽度后的距离时表示已经到达轨道的最右边了
 if (moveX >= this.$$root.offsetWidth - (x2 - x1)) {
  this.$$isSuccess = true
  this.$$text.textContent = "验证成功"
  this.$$text.style.cssText = `color: #fff; left: 0; right: ${this.$$block.offsetWidth}px`
  this.$$block.classList.add("success")
 }
}

这里的实现也很简单,唯一需要看一下的就是通过 getBoundingClientRect 来获取了滑块相对于视口的位置,然后根据鼠标所在的位置来判断鼠标是否在滑块上,如果不在则取消移动。

现在它已经能很好的滑动,并完成提示成功的基本功能了。但是,当我们每次滑动到中间就取消,然后再次点击滑动时,就会导致重复的添加滑动事件,而且中途释放后,滑块就停在了当前位置,这显然不对。

解决的办法就是在添加鼠标按下事件的时候,同时也指定一个松开的事件,在这个事件的监听器中判断如果没有成功则取消之前绑定的滑动事件,并进行重置,为了看起来更友好,我们还可以加上一点动画。

class SlideUnlock {
 init() {
  /* ... */
  document.addEventListener(
   "mouseup",
   (this.$$handleMouseUp = this._handleMouseUp.bind(this)),
   false
  )
 }

 _handleMouseDown(e) {
  /* ... */
  // 取消在手动滑动过程中的动画效果
  this.$$bg.style.transition = ""
  this.$$block.style.transition = ""
  /* ... */
 }

 _handleMouseUp(e) {
  this.$$block.removeEventListener(
   "mousemove",
   this.$$handleMouseMove,
   false
  )

  if (this.$$isSuccess) {
   return
  }

  // 给重置过程添加动画效果
  this.$$bg.style.transition = "width 1s ease"
  this.$$block.style.transition = "left 1s ease"

  this.$$block.style.left = 0
  this.$$bg.style.width = 0
 }
}

目前为止,滑块已经可以在 PC 端正常的工作了,不过在移动端却并不理想。

为了它能够在移动端也可以很好的工作,我们可以借助 touchstarttouchmovetouchend 等事件来完成。

绑定这些事件的时机和处理方式和之前的三个事件分别相对应,所以我们新增两个方法(和 jQuery 的 on、off 方法很像)来更好的添加和移除事件。

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)
 })
}

根据这两个方法,我们来稍微修改一下滑块中添加事件的代码。

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)
 })
}

另外,需要注意的是在移动端 touch 事件中获取 clientXclientY 时不能在事件对象上直接读取,而是在 event.changedTouches[0] 对象上取得。

现在,它已经能够同时在 PC 端和移动端上工作了,不过我们还能对它进行一些优化,比如使用函数节流。

函数节流的实现方式有很多,这里列一下我们在本次过程中使用的方式。

utils.throttle = function(method, context = {}, delay = 4, ...outParams) {
 return function(...innerParams) {
 clearTimeout(context.$$tId)
 context.$$tId = setTimeout(function() {
  method.apply(context, [...outParams, ...innerParams])
 }, delay)
 }
}

然后用这个节流函数,来包装我们移动时的处理函数,并根据实际情况做点调整。

除此之外,我们还可以添加一个重置的方法,让它回到最初的状态,涉及到的内容也很简单,就是在成功的状态下重新设置样式和绑定事件。

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()
}

好了,滑块的实现到这里就告一段落了,相信大家看到这里已经完全明白了,甚至有更好的实现。

如何使用

你可以简单的在 HTML 页面中引入该脚本,然后根据自己的需求设置合适的样式;不过更好的方式是通过这样的思路,在项目中做一些改进(比如平滑降级)等处理。

接下来是一个简单的使用模板。

<body>
 <script src="slide-unlock/core.js"></script>
 <script>
  const slider = new SlideUnlock()
  slider.init()
 </script>
</body>

你可以在 这里 看见完整的使用方式和效果。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
新闻内页-JS分页
Jun 07 Javascript
JS操作数据库的实例代码
Oct 17 Javascript
解决JQeury显示内容没有边距内容紧挨着浏览器边线
Dec 20 Javascript
jQuery插件MixItUp实现动画过滤和排序
Apr 12 Javascript
JS实现三个层重叠点击互相切换的方法
Oct 06 Javascript
jQuery计算文本框字数及限制文本框字数的方法
Mar 01 Javascript
JavaScript中的splice方法用法详解
Jul 20 Javascript
vue 点击按钮增加一行的方法
Sep 07 Javascript
优雅的处理vue项目异常实战记录
Jun 05 Javascript
深入学习Vue nextTick的用法及原理
Oct 08 Javascript
vuex(vue状态管理)的特殊应用案例分享
Mar 03 Javascript
JavaScript 中判断变量是否为数字的示例代码
Oct 22 Javascript
微信小程序 自定义弹窗实现过程(附代码)
Dec 05 #Javascript
Nuxt v-bind绑定img src不显示的解决
Dec 05 #Javascript
微信小程序swiper左右扩展各显示一半代码实例
Dec 05 #Javascript
微信小程序顶部导航栏可滑动并选中放大
Dec 05 #Javascript
微信小程序实现点击按钮后修改颜色
Dec 05 #Javascript
React 父子组件通信的实现方法
Dec 05 #Javascript
Javascript中的this,bind和that使用实例
Dec 05 #Javascript
You might like
用PHP写的基于Memcache的Queue实现代码
2011/11/27 PHP
php使用Jpgraph绘制饼状图的方法
2015/06/10 PHP
PHP的Yii框架中行为的定义与绑定方法讲解
2016/03/18 PHP
浅析php-fpm静态和动态执行方式的比较
2016/11/09 PHP
在线编辑器中换行与内容自动提取
2009/04/24 Javascript
jQuery对象和DOM对象使用说明
2010/06/25 Javascript
7个jQuery最佳实践
2016/01/12 Javascript
学JavaScript七大注意事项【必看】
2016/05/04 Javascript
使用node打造自己的命令行工具方法教程
2018/03/26 Javascript
详解小程序输入框闪烁及重影BUG解决方案
2018/08/31 Javascript
深入理解Vue 的钩子函数
2018/09/05 Javascript
使用Angular9和TypeScript开发RPG游戏的方法
2020/03/25 Javascript
autojs 蚂蚁森林能量自动拾取即给指定好友浇水的实现方法
2020/05/03 Javascript
JS控制下拉列表左右选择实例代码
2020/05/08 Javascript
原生JS实现记忆翻牌游戏
2020/07/31 Javascript
node使用async_hooks模块进行请求追踪
2021/01/28 Javascript
[46:27]DOTA2上海特级锦标赛主赛事日 - 1 胜者组第一轮#2LGD VS MVP.Phx第一局
2016/03/02 DOTA
Python实现的简单万年历例子分享
2014/04/25 Python
python检测某个变量是否有定义的方法
2015/05/20 Python
python使用urllib2提交http post请求的方法
2015/05/26 Python
Python yield 使用浅析
2015/05/28 Python
理解Python中的With语句
2016/03/18 Python
python实现随机梯度下降法
2020/03/24 Python
在PyCharm的 Terminal(终端)切换Python版本的方法
2019/08/02 Python
Python使用指定字符长度切分数据示例
2019/12/05 Python
Python爬虫爬取电影票房数据及图表展示操作示例
2020/03/27 Python
银行办公室岗位职责
2014/03/10 职场文书
教师职业道德事迹材料
2014/08/18 职场文书
家长学校培训材料
2014/08/20 职场文书
镇创先争优活动总结
2014/08/28 职场文书
2014乡镇干部纪律作风整顿思想汇报
2014/09/13 职场文书
2014新生大学四年计划书
2014/09/21 职场文书
怎样写家长意见
2015/06/04 职场文书
​(迎国庆)作文之我爱我的祖国
2019/09/19 职场文书
教你怎么用python爬取爱奇艺热门电影
2021/05/20 Python
TV动画《史上最强大魔王转生为村民A》番宣CM公布
2022/04/01 日漫