前言

本次给大家分享的是常见的移动端单点触摸事件的设计思路及实践。

核心技术

主要就是利用移动端的以下3个触摸事件,来模拟和实现自定义的手势操作

  • touchstart:手指触摸到屏幕的一瞬间触发
  • touchmove:手指在屏幕上移动时触发
  • touchend:手指从屏幕上离开时触发

概念梳理

touch事件触发时,有3组数据可以获得触摸信息,可能大家会对这几组数据有些混淆,我根据自己的理解来尽量用通俗的语言给大家解释清楚

  • touches:整个屏幕上所有的触摸点集合
  • targetTouches:当前DOM元素上的所有触摸点集合
  • changedTouches:相对上一次触摸点发生变化的集合

我们先来看一张图

如图所示,我们在节点B上绑定touch事件,圆圈代表触摸点。

此时节点B有3个触摸点,即targetTouches数组有3项,分别储存着触摸点的信息,此时touchestargetTouches是相同的。

当我们将手指3移出节点B(始终保持3个手指触摸在屏幕上),那么touchmove事件触发,targetTouches只剩2项,而touches依然有3项,此时changedTouches只有一项(因为只有手指3改变了)。

然后我们让所有手指离开屏幕,那么此时touchend事件触发,touches只剩0项,targetTouches剩0项,changedTouches有3项(因为3个手指发生了变化)。

好了,理解这些概念,有助于我们理解代码中何时改去哪个touch数组里面的值。

思路及实践

tap

tap可以理解为点击事件,和click不同的是,移动端的click事件有大约300ms的延迟,这是因为浏览器要判断是否为双击事件。

思路

  • touchstart:时记录时间点以及触摸点的x、y坐标
  • touchend:计算此时与开始时的时间差,水平和垂直方向的偏移量

说明:时间差用来判断用户触摸的时长,超过规定时间则tap事件无效;偏移量用来判断用户的触摸事件内是否有过移动的痕迹,这里我们允许少量的偏移,因为手指可能出现抖动的情况

实现

const tapDefaults = {time: 250,offset: 10
}export default function tap (node, a, b) {let st, sx, sylet opts, callbackif (typeof a === 'function') {callback = aopts = Object.assign({}, tapDefaults, b)} else {callback = bopts = Object.assign({}, tapDefaults, a)}node.addEventListener('touchstart', (e) => {e.preventDefault() // 组织浏览器默认行为,防止触摸过程页面滚动const touch = e.targetTouches[0]st = e.timeStampsx = touch.pageXsy = touch.pageY}, false)node.addEventListener('touchend', (e) => {const touch = e.changedTouches[0]if (// 若为长按,则将时间判定条件更改e.timeStamp - st <= opts.time &&Math.abs(touch.pageX - sx) <= opts.offset &&Math.abs(touch.pageY - sy) <= opts.offset) {callback && callback()}}, false)
}
复制代码

doubletap

即双击事件,两次点击时间间隔不超过规定时间则视为有效。

思路

  • 第一次有效点击,记录该状态,反之重置状态
  • 第二次有效点击,触发事件并重置状态
  • 若两次时间间隔过长,重置状态

实现

const tapDefaults = {time: 250,offset: 10
}function handler (node, inject) {let st, sx, synode.addEventListener('touchstart', (e) => {e.preventDefault()const touch = e.targetTouches[0]st = e.timeStampsx = touch.pageXsy = touch.pageY}, false)node.addEventListener('touchend', (e) => {const touch = e.changedTouches[0]inject({time: e.timeStamp - st,offsetX: Math.abs(touch.pageX - sx),offsetY: Math.abs(touch.pageY - sy)})}, false)
}export function doubletap (node, a, b) {let opts, callbacklet status = 0if (typeof a === 'function') {callback = aopts = Object.assign({}, tapDefaults, b)} else {callback = bopts = Object.assign({}, tapDefaults, a)}handler(node, (info) => {if (info.time <= opts.time &&info.offsetX <= opts.offset &&info.offsetY <= opts.offset) {if (status === 0) {status = 1// 时间间隔太长则重置状态setTimeout(() => {status = 0}, opts.time)} else if (status === 1) {callback && callback()status = 0}} else {status = 0}})
}
复制代码

longtap

即长按,手指按住超过规定时间视为有效,在手指离开时触发。

思路

  • 和tap事件思路一样,只不过时间的判定条件变更一下,改为超过多长时间才触发

实现

const longtapDefaults = {time: 350,offset: 10
}// 这里代码逻辑和tap事件一样
// 更改时间判定为:
// e.timeStamp - st > opts.time
复制代码

press

即按压事件,按住超过规定时间自动触发,注意和longtap不同的是,longtap需要等到手指离开时触发,而press在按压时间达到规定值,自动触发,此时手指还在屏幕上。

思路

  • touchstart:记录此时的x、y坐标,并且开启一个定时器,在规定时间后执行回调,默认是350ms
  • touchmove:监听移动过程,在事件触发前,若出现偏移量过大,则取消定时器
  • touchend:取消定时器

分析:根据以上思路,若按压时间短,则手指离开时定时器已取消,回调不会触发。

实现

const pressDefaults = {time: 350,offset: 10
}export default function press (node, a, b) {let opts, callback, sx, sylet timer = nullif (typeof a === 'function') {callback = aopts = Object.assign({}, pressDefaults, b)} else {callback = bopts = Object.assign({}, pressDefaults, a)}node.addEventListener('touchstart', (e) => {e.preventDefault()const touch = e.targetTouches[0]sx = touch.pageXsy = touch.pageYtimer = setTimeout(() => {callback && callback()}, opts.time)}, false)node.addEventListener('touchmove', (e) => {const touch = e.targetTouches[0]if (Math.abs(touch.pageX - sx) > opts.offset ||Math.abs(touch.pageY - sy) > opts.offset) {clearTimeout(timer)}}, false)node.addEventListener('touchend', () => {clearTimeout(timer)}, false)
}
复制代码

swipe

即手指滑动事件,应用场景如:轮播图左右滑动切换,整屏页面滑动翻页等,算是移动端最常见的手势之一了。

思路

  • touchstart:记录时间点和触摸点位置
  • touchmove:实时判断滑动偏移量
  • touchend:计算滑动速度和方向,条件判定是否触发事件

分析:考虑到需要在滑动过程做一些动画特效等操作,因此我们将滑动中的事件暴露给用户自定义,值得注意的是,若是要实时改变滑块位置的话,最好不要截流或防抖,截流会造成滑动卡顿的现象,而防抖会出现延迟同步滑动操作的情况;另外对滑动速度也进行了处理,原则上用户滑动距离超过规定后即视为有效,然而为了更好的用户体验,我们判定,如果用户在短时间内滑动速度非常快的话,也视为一次有效的操作,不一定非要滑动很长的距离

实现

const swipeDefaults = {direction: 'horizontal', // verticalspeed: 200,offset: 100,prevent: true,// touchmove: (offset) => {}
}export default function swipe (node, a, b) {let opts, callback, sTime, sTouch, eTouchif (typeof a === 'function') {callback = aopts = Object.assign({}, swipeDefaults, b)} else {callback = bopts = Object.assign({}, swipeDefaults, a)}node.addEventListener('touchstart', (e) => {if (opts.prevent) {e.preventDefault()}sTime = e.timeStampsTouch = eTouch = e.targetTouches[0]}, false)if (typeof opts.touchmove === 'function') {node.addEventListener('touchmove', (e) => {eTouch = e.targetTouches[0]if (opts.direction === 'horizontal') {opts.touchmove(eTouch.pageX - sTouch.pageX)} else {opts.touchmove(eTouch.pageY - sTouch.pageY)}}, false)}node.addEventListener('touchend', (e) => {eTouch = e.changedTouches[0]let time = e.timeStamp - sTimelet offset, directionif (opts.direction === 'horizontal') {offset = eTouch.pageX - sTouch.pageXdirection = offset > 0 ? 'right' : 'left'} else {offset = eTouch.pageY - sTouch.pageYdirection = offset > 0 ? 'down' : 'up'}if (Math.abs(offset) >= opts.offset ||Math.abs(offset) / time * 1000 >= opts.speed) {callback && callback(direction)}}, false)
}
复制代码

结束语

通过以上的思路讲解和代码实现,我们完成了一个单点触控的移动端手势库,是不是迫不及待的想要一睹为快、体验一番。

最后附上本次分享的源码和文档:github.com/ansenhuang/…

移动端手势库设计与实践相关推荐

  1. RocketMQ 端云一体化设计与实践

    简介:本文首先介绍了端云消息场景一体化的背景,然后重点分析了终端消息场景特点,以及终端消息场景支撑模型,最后对架构和存储内核进行了阐述.我们期望基于 RocketMQ 统一内核一体化支持终端和服务端不 ...

  2. 美团民宿跨端复用框架设计与实践

    从 PC 时代.移动时代到万物互联的 IoT 时代,伴随终端设备的日趋多样化,跨端复用的种子自此落地,开始生根发芽.从依靠容器能力.各类离线化预装包的 Hybrid 方案,到通过 JSC 连接 Jav ...

  3. 移动端手势库Hammer.js学习

    感觉移动端原生支持的 touch.tap.swipe 几个事件好像还不够用,某些时候还会用到诸如缩放.长按等其他功能. 近日学习了一个手势库 Hammer.js,它是一个轻量级的触屏设备手势库,能识别 ...

  4. 服务端监控架构设计与实践

    作者:vivo互联网服务器团队-Deng Haibo 一.业务背景 当今时代处在信息大爆发的时代,信息借助互联网的潮流在全球自由的流动,产生了各式各样的平台系统和软件系统,越来越多的业务也会导致系统的 ...

  5. 爱奇艺移动端APP健壮性测试的设计与实践

    ‍ ‍ 01 前言 一款APP的发布以及新功能的迭代需要经过严格的质量保证,而崩溃性问题是影响APP稳定的头号问题.其中,因前端不兼容后端服务数据格式变更而引起的崩溃问题占有一定的比例.这类崩溃问题一 ...

  6. 移动端cube界面设计html,滴滴 Web 移动端组件库 cube-ui 开源

    滴滴 WebApp 团队在去年底用 Vue.js 2.0 对业务进行重构,并开发了一套移动端组件库 cube-ui 支撑业务的开发.经过了一年多的业务考验,cube-ui 也日趋成熟,而且我们相信除了 ...

  7. 如何防止删库跑路?运维堡垒机高效安全运维设计与实践落地

    在刚刚结束的 2020 全球新一代软件工程线上峰会上,有着近七年自动化运维平台研发经验的京东智联云产品架构师任龙涛,分享了<运维堡垒机高效安全运维设计与实践落地>议题.本篇文章将为大家回顾 ...

  8. hammer.js移动端开发手势库

    原文转载自https://www.cnblogs.com/vajoy/p/4011723.html hammerJS是一个优秀的.轻量级的触屏设备手势库,现在已经更新到2.04版本,跟1.0版本有点天 ...

  9. 移动端cube界面设计html,滴滴开源基于 Vue.js 的移动端组件库 cube-ui

    原标题:滴滴开源基于 Vue.js 的移动端组件库 cube-ui 开源最前线(ID:OpenSourceTop) 猿妹 整编 综合自:https://didi.github.io/cube-ui/ ...

最新文章

  1. Nature对数千篇论文提出质疑:隐藏的细菌蛋白为自然界的“电网”提供动力
  2. SpreadJS 在 Angular2 中支持绑定哪些属性?
  3. Linux下 ln 命令详解
  4. DataSet导出Excel,比以往的方法导出的Excel外观更加好看
  5. 连接mysql报zone时区错误
  6. 使用WebIDE开发Android应用
  7. php视频上传教程,PHP实现视频文件上传完整实例,_PHP教程
  8. 2.9.JavaScript--内置对象
  9. 在操作系统重启后恢复应用程序的工作状态
  10. 正则 null_正则表达式exec、match、test的区别
  11. Java并发编程实战————对象的组合
  12. 金融冬天 IT产业如何应对危险与机遇
  13. CentOS_6配置163网络yum源
  14. 【pyhive】本地使用pyhive连接hive数据库踩的坑
  15. 【元宇宙经济学】元宇宙经济的四个特征
  16. CSS的动画特效(animation)
  17. JavaScript一线大厂面试秘籍:面向对象+dom\bom+事件+特性\动画+面试题+基础
  18. 金融财务英译汉常用词怎样翻译
  19. 【华为云技术分享】10分钟快速在华为云鲲鹏弹性云服务器上部署一个自己的弹幕网站!
  20. 数据结构--栈的基本概念与应用

热门文章

  1. TP收集一些可以用的资源
  2. CF 398 E(动态规划)
  3. 从后台获取的数据渲染到页面中的dom操作
  4. jquery的img的动态title换行
  5. JAVA 的StringBuffer类
  6. 自动驾驶感知-车道线系列(二)——Canny边缘检测
  7. OSG仿真案例(7)——osg自动驾驶
  8. 计算机鼠标样式,告诉你电脑如何更改鼠标的指针样式?
  9. zabbix agent安装_zabbix agent的安装与配置
  10. ubuntu安装python3.6_Ubuntu16.04下安装python3.6.4详细步骤