Дизайн и практика мобильной библиотеки жестов

внешний интерфейс GitHub

предисловие

На этот раз я поделюсь с вами дизайнерскими идеями и практиками обычных мобильных событий в одно касание.

Основная технология

В основном это использование следующих трех сенсорных событий на мобильном терминале для имитации и реализации пользовательских операций жестов.

  • touchstart: срабатывает, когда палец касается экрана
  • touchmove: срабатывает при движении пальца по экрану
  • touchend: срабатывает, когда палец покидает экран

Концепция кардинга

Когда событие касания срабатывает, есть 3 набора данных, которые могут получить информацию о касании. Вы можете быть немного сбиты с толку этими наборами данных. Я постараюсь объяснить это вам простым языком в соответствии с моим собственным пониманием.

  • касания: коллекция всех точек касания на всем экране
  • targetTouches: коллекция всех точек касания текущего элемента DOM.
  • changeTouches: набор изменений относительно последней точки касания.

Давайте сначала посмотрим на картинку

Как показано на рисунке, мы привязываем событие касания к узлу B, а кружок представляет собой точку касания.

На данный момент узел B имеет 3 точки касания, а именноtargetTouchesМассив имеет 3 элемента, которые хранят информацию о точке касания соответственно.touchesа такжеtargetTouchesПодобные.

Когда мы перемещаем палец 3 из узла B (всегда держите 3 пальца касающимися экрана), срабатывает событие touchmove,targetTouchesОсталось всего 2 штуки иtouchesНа этот раз их еще троеchangedTouchesЕсть только один (потому что только палец 3 меняется).

Затем отпускаем все пальцы от экрана, тогда в это время срабатывает событие touchend,touchesОсталось всего 0 шт.,targetTouchesосталось 0 шт.,changedTouchesТам 3 пункта (потому что 3 пальца менялись).

Что ж, понимание этих концепций помогает нам понять, когда менять значение, в каком сенсорном массиве в коде.

Идеи и практика

tap

TAP можно понимать как событие щелчка, а щелчок отличается, событие щелчка мобильного устройства имеет задержку примерно 300 мс, потому что браузер должен определить, следует ли дважды щелкнуть событие.

идеи

  • touchstart: запишите момент времени и координаты x и y точки касания
  • touchend: рассчитать разницу во времени между этим временем и началом, а также смещение по горизонтали и вертикали

Описание: разница во времени используется для оценки продолжительности касания пользователя. Если время превышает указанное время, событие касания является недействительным; смещение используется для определения наличия каких-либо следов движения в событии касания пользователя. Здесь допускаем небольшое смещение, потому что пальцы могут трясти корпус

выполнить

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

То есть длительное нажатие, палец удерживается более указанного времени как действительное, и срабатывает, когда палец убирается.

идеи

  • Идея аналогична событию касания, за исключением того, что изменено условие оценки времени, и оно изменено на то, сколько времени требуется для срабатывания.

выполнить

const longtapDefaults = {
  time: 350,
  offset: 10
}

// 这里代码逻辑和tap事件一样
// 更改时间判定为:
// e.timeStamp - st > opts.time

press

То есть событие нажатия, которое автоматически запускается нажатием и удержанием в течение более заданного времени, обратите внимание иlongtapРазница в том, что при длительном касании нужно ждать, пока палец не уйдет, чтобы сработать, в то время какpressКогда время нажатия достигает заданного значения, оно срабатывает автоматически, а палец в это время все еще находится на экране.

идеи

  • touchstart: запишите координаты x и y в это время и запустите таймер для выполнения обратного вызова после указанного времени, по умолчанию 350 мс
  • 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)
}

заключительные замечания

Благодаря приведенным выше идеям и реализации кода мы создали библиотеку жестов для мобильного терминала одним касанием. Не терпится увидеть ее и испытать на себе.

Наконец, исходный код и документы, предоставленные на этот раз, прилагаются:GitHub.com/Энсон Вонг/...