资讯专栏INFORMATION COLUMN

移动端手势库设计与实践

wudengzan / 3396人阅读

摘要:前言本次给大家分享的是常见的移动端单点触摸事件的设计思路及实践。实现即手指滑动事件,应用场景如轮播图左右滑动切换,整屏页面滑动翻页等,算是移动端最常见的手势之一了。

前言

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

核心技术

主要就是利用移动端的以下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, sy
  let opts, callback

  if (typeof a === "function") {
    callback = a
    opts = Object.assign({}, tapDefaults, b)
  } else {
    callback = b
    opts = Object.assign({}, tapDefaults, a)
  }

  node.addEventListener("touchstart", (e) => {
    e.preventDefault() // 组织浏览器默认行为,防止触摸过程页面滚动

    const touch = e.targetTouches[0]
    st = e.timeStamp
    sx = touch.pageX
    sy = 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, sy

  node.addEventListener("touchstart", (e) => {
    e.preventDefault()

    const touch = e.targetTouches[0]
    st = e.timeStamp
    sx = touch.pageX
    sy = 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, callback
  let status = 0

  if (typeof a === "function") {
    callback = a
    opts = Object.assign({}, tapDefaults, b)
  } else {
    callback = b
    opts = 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, sy
  let timer = null

  if (typeof a === "function") {
    callback = a
    opts = Object.assign({}, pressDefaults, b)
  } else {
    callback = b
    opts = Object.assign({}, pressDefaults, a)
  }

  node.addEventListener("touchstart", (e) => {
    e.preventDefault()

    const touch = e.targetTouches[0]
    sx = touch.pageX
    sy = touch.pageY

    timer = 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", // vertical
  speed: 200,
  offset: 100,
  prevent: true,
  // touchmove: (offset) => {}
}

export default function swipe (node, a, b) {
  let opts, callback, sTime, sTouch, eTouch

  if (typeof a === "function") {
    callback = a
    opts = Object.assign({}, swipeDefaults, b)
  } else {
    callback = b
    opts = Object.assign({}, swipeDefaults, a)
  }

  node.addEventListener("touchstart", (e) => {
    if (opts.prevent) {
      e.preventDefault()
    }

    sTime = e.timeStamp
    sTouch = 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 - sTime
    let offset, direction

    if (opts.direction === "horizontal") {
      offset = eTouch.pageX - sTouch.pageX
      direction = offset > 0 ? "right" : "left"
    } else {
      offset = eTouch.pageY - sTouch.pageY
      direction = offset > 0 ? "down" : "up"
    }

    if (
      Math.abs(offset) >= opts.offset ||
      Math.abs(offset) / time * 1000 >= opts.speed
    ) {
      callback && callback(direction)
    }
  }, false)
}
结束语

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

最后附上本次分享的源码和文档:https://github.com/ansenhuang/axe/blob/master/packages/touch/README.md

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/93723.html

相关文章

  • HTML5中手势原理分析数学知识的实践

    摘要:中手势原理分析与数学知识的实践引言在这触控屏的时代,人性化的手势操作已经深入了我们生活的每个部分。这篇博文主要是解析了移动端常用手势的原理,及从前端的角度学习过程中所使用的数学知识。 HTML5中手势原理分析与数学知识的实践 引言 在这触控屏的时代,人性化的手势操作已经深入了我们生活的每个部分。现代应用越来越重视与用户的交互及体验,手势是最直接且最为有效的交互方式,一个好的手势交互,能...

    rollback 评论0 收藏0
  • vue 在移动体验上的优化解决方案

    摘要:去年年底自己搭了一个在移动端的开发框架,感觉体验不是很好。路由懒加载首页终于写完了,以上这些就是我在移动端体验优化的实战。去年年底自己搭了一个vue在移动端的开发框架,感觉体验不是很好。上个星期又要做移动端的项目了。所以我花了两天时间对之前的那个开发框架做了以下优化 自定义vuex-plugins-loading 路由切换动画 + keep alive 动态管理缓存组件 better-sc...

    godlong_X 评论0 收藏0
  • 单点手势

    摘要:单点手势库分析手势是什么有哪些方法实现首先我这里指的手势是指我们在移动端进行触屏交互的时候,用户操作的一些手势。可以看地址总结这是我挺久之前做的移动端单点手势库学习时参考剧中人你可以在这里找到我个人网站 单点手势库 分析 手势是什么? 有哪些方法实现? 首先我这里指的手势是指我们在移动端进行触屏交互的时候,用户操作的一些手势。在我们在移动端需要一些交互的时候。难免有时候需要左滑右...

    jollywing 评论0 收藏0
  • 单点手势

    摘要:单点手势库分析手势是什么有哪些方法实现首先我这里指的手势是指我们在移动端进行触屏交互的时候,用户操作的一些手势。可以看地址总结这是我挺久之前做的移动端单点手势库学习时参考剧中人你可以在这里找到我个人网站 单点手势库 分析 手势是什么? 有哪些方法实现? 首先我这里指的手势是指我们在移动端进行触屏交互的时候,用户操作的一些手势。在我们在移动端需要一些交互的时候。难免有时候需要左滑右...

    FuisonDesign 评论0 收藏0
  • 单点手势简单思考实现

    摘要:单点手势库分析手势是什么有哪些方法实现首先我这里指的手势是指我们在移动端进行触屏交互的时候,用户操作的一些手势。可以看地址总结这是我挺久之前做的移动端单点手势库个人博客地址学习时参考剧中人 单点手势库 分析 手势是什么? 有哪些方法实现? 首先我这里指的手势是指我们在移动端进行触屏交互的时候,用户操作的一些手势。在我们在移动端需要一些交互的时候。难免有时候需要左滑右滑。 目前市面...

    ybak 评论0 收藏0

发表评论

0条评论

wudengzan

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<