@whee/js-motion

motion for mobile

Usage no npm install needed!

<script type="module">
  import wheeJsMotion from 'https://cdn.skypack.dev/@whee/js-motion';
</script>

README

Motion

Build Status codecov code style: prettier Commitizen friendly

移动端触摸手势检测工具,根据元素的 touchstart, touchmove, touchend 事件计算相关的滑动、缩放和旋转操作,以及触摸事件结束后的惯性滑动。

滑动、缩放以及旋转都是当前时刻相对元素上一时刻的状态计算的,因此从触摸开始(touchstart)到触摸结束(touchend)之间总的滑动距离和旋转角度,需要将每一时刻值进行累加,而缩放大小则需要累乘

示例

触摸板

使用

npm install @whee/js-motion

自动监听 options.target 元素的手势操作:

import Motion from '@whee/js-motion'

const motion = new Motion({
  target: '#target',
  mode: 'realtime',
  direction: 'xy'
})

motion.onTouchstart(e => {
  // ...
})
motion.onTouchmove(({ x, y, scale, angle }, e) => {
  // ...
})
motion.onTouchend(({ x, y }, e) => {
  // ...
})

如果已经进行了目标元素的 touch 事件绑定,则需要传入相关的事件,便可以检测到 event.target 元素的手势操作:

import Motion from '@whee/js-motion'

const motion = new Motion({
  mode: 'realtime',
  direction: 'xy'
})
const target = document.querySelector('#target')

target.addEventListener(
  'touchstart',
  e => {
    e.preventDefault()
    motion.touchstart(e)
    // 其他操作
    //...
  },
  Motion.isSupportPassive ? { passive: false, capture: true } : false
)

target.addEventListener(
  'touchmove',
  e => {
    e.preventDefault()
    motion.touchmove(e, ({ x, y, scale, angle }) => {
      // ...
    })
    // 其他操作
    // ...
  },
  Motion.isSupportPassive ? { passive: false, capture: true } : false
)

target.addEventListener(
  'touchend',
  e => {
    motion.touchend(e, ({ x, y }) => {
      // ...
    })
    // 其他操作
    // ...
  },
  Motion.isSupportPassive ? { passive: false, capture: true } : false
)

推荐使用第一种方式,motion 实例会自动监听 options.target 元素的相关事件。

Mode

touchmove 的执行模式:

  • 'realtime' - 实时模式:

    即每一次 touchmove 事件进行处理计算滑动距离。

  • 'frame' - 帧模式:

    即一帧内的多个 touchmove 事件合并,进行一次处理,计算总的滑动距离。

注意:如果可以尽量使用 realtime 模式,因为部分浏览器对 touchmove 事件触发的频率已经做了优化,使用 frame 会出现跨度较大的问题。

Direction

motion 监听的移动方向:

  • 'x' - 只监听水平方向的移动:

    只计算水平方向的移动距离,垂直方向的移动距离始终为 0。

  • 'y' - 只监听垂直方向的移动。

    只计算垂直方向的移动距离,水平方向的移动距离始终为 0。

  • 'xy' - 同时监听水平和垂直方向的移动。

    同时计算水平和垂直两个方向的移动距离。

Coordinate

参考的坐标系:

  • 'screen' - 使用 touch.screenXtouch.screenY 计算数据
  • 'client' - 使用 touch.clientXtouch.clientY 计算数据
  • 'page' - 使用 touch.pageXtouch.pageY 计算数据

Motion

const motion = new Motion(options)

实例化时指定 options.target 选项,被动监听 motion.touch

options

  • options.target - 需要监听触摸事件的目标元素,可选。
  • options.mode - 指定 touchmove 的执行模式,默认为 'realtime'
  • options.direction - 指定监听的移动方向,默认为 'xy'
  • options.coordinate - 参考的坐标系,默认为 'page'

motion.onTouchstart(cb)

监听 options.targettouchstart 操作,并将 touchstart 事件回调。

motion.onTouchstart(e => {
  // e 是 options.target 的 touchstart 事件
})

motion.onTouchmove(cb)

监听 options.targettouchmove 操作,并将滑动距离和 touchmove 事件回调。

motion.onTouchmove(({ x, y, scale, angle }, e) => {
  // x 是水平方向的移动距离
  // y 是垂直方向的移动距离
  // scale 是缩放比率
  // angle 是旋转的角度
  // e 是 options.target 元素的原生 touchmove 事件
})

motion.onTouchend(cb)

监听 options.targettouchend 操作,并将需要惯性滑动的距离和 touchend 事件进行回调。

回调的滑动距离为 0 时则表示滑动停止:

  • options.direction='x' - 回调惯性滑动距离分量 x0 时,表示惯性滑动停止
  • options.direction='y' - 回调惯性滑动距离分量 y0 时,表示惯性滑动停止
  • options.direction='xy' - 回调惯性滑动距离分量 xy 都为 0 时,表示惯性滑动停止

注意:如果没触发惯性滑动,只会触发一次回调;如果触发惯性滑动,则会进行多次回调,直到滑动停止。

motion.onTouchend(({ x, y }, e) => {
  // x 是水平方向的惯性移动距离
  // y 是垂直方向的惯性移动距离
  // e 是 options.target 元素的原生 touchend 事件
})

motion.touchstart(e)

实例化时没有指定 options.target 元素,则需要主动调用该方法,并传入 touchstart 事件,告诉 motion 实例滑动操作即将开始。

const target = document.querySelector('#target')

target.addEventListener(
  'touchstart',
  e => {
    // 主动传入 touchstart 事件,触摸开始
    motion.touchstart(e)
  },
  Motion.isSupportPassive ? { passive: false, capture: true } : false
)

注意:在调用 motion.touchmove() 之前始终需要先调用一次 motion.touchstart(),保证 motion 可以正确计算开始触摸的位置。

motion.touchmove(e, cb)

实例化时没有指定 options.target 元素,则需要主动调用该方法,并传入 touchmove 事件,告诉 motion 实例滑动操作正在进行。

const target = document.querySelector('#target')

target.addEventListener(
  'touchmove',
  e => {
    // 主动传入 touchmove 事件,正在滑动
    motion.touchmove(e, ({ x, y, scale, angle }) => {
      // x 是水平方向的滑动距离
      // y 是垂直方向的滑动距离
      // scale 是缩放比率
      // angle 是旋转的角度
    })
  },
  Motion.isSupportPassive ? { passive: false, capture: true } : false
)

motion.touchend(e, cb)

实例化时没有指定 options.target 元素,则需要主动调用该方法,并传入 touchend 事件,告诉 motion 实例触摸已经结束。达到惯性滑动条件,则会进行惯性滑动。

const target = document.querySelector('#target')

target.addEventListener(
  'touchend',
  e => {
    // 主动传入 touchend 事件,触摸结束
    motion.touchend(e, ({ x, y }) => {
      // x 是水平方向的惯性滑动距离
      // y 是垂直方向的惯性滑动距离
    })
  },
  Motion.isSupportPassive ? { passive: false, capture: true } : false
)