Сенсорные события и жесты

Основы сенсорных событий

Сенсорные события в браузере позволяют отслеживать взаимодействие пользователя с устройствами ввода, такими как сенсорные экраны. В Vue.js обработка таких событий осуществляется через директиву v-on, которая связывает события DOM с методами компонента.

Основные события сенсорного ввода:

  • touchstart — возникает при начале касания экрана.
  • touchmove — возникает при перемещении пальца по экрану.
  • touchend — возникает при окончании касания.
  • touchcancel — возникает при отмене касания системой (например, при появлении уведомления).

Пример базового использования:

<template>
  <div
    class="touch-area"
    @touchstart="onTouchStart"
    @touchmove="onTouchMove"
    @touchend="onTouchEnd"
  >
    Сенсорная область
  </div>
</template>

<script>
export default {
  methods: {
    onTouchStart(event) {
      console.log('Касание началось', event.touches[0].clientX, event.touches[0].clientY);
    },
    onTouchMove(event) {
      console.log('Касание движется', event.touches[0].clientX, event.touches[0].clientY);
    },
    onTouchEnd(event) {
      console.log('Касание завершено');
    }
  }
}
</script>

<style scoped>
.touch-area {
  width: 300px;
  height: 200px;
  background-color: #f0f0f0;
  border: 1px solid #ccc;
  display: flex;
  align-items: center;
  justify-content: center;
  user-select: none;
}
</style>

Ключевой момент: объект события event содержит массив touches — список всех активных касаний. Для определения конкретного касания используется индекс [0], если требуется работать с первым касанием.

Обработка жестов

Жесты — это комбинации сенсорных событий, интерпретируемых как определённые действия: свайпы, пинч-зум, вращение. Vue.js не предоставляет встроенной системы распознавания жестов, но её можно реализовать через вычисление изменения координат касаний.

Пример реализации свайпа:

<template>
  <div
    class="swipe-area"
    @touchstart="startSwipe"
    @touchend="endSwipe"
  >
    Проведите пальцем
  </div>
</template>

<script>
export default {
  data() {
    return {
      startX: 0,
      startY: 0,
    }
  },
  methods: {
    startSwipe(event) {
      this.startX = event.touches[0].clientX;
      this.startY = event.touches[0].clientY;
    },
    endSwipe(event) {
      const endX = event.changedTouches[0].clientX;
      const endY = event.changedTouches[0].clientY;
      const deltaX = endX - this.startX;
      const deltaY = endY - this.startY;

      if (Math.abs(deltaX) > Math.abs(deltaY)) {
        if (deltaX > 50) {
          console.log('Свайп вправо');
        } else if (deltaX < -50) {
          console.log('Свайп влево');
        }
      } else {
        if (deltaY > 50) {
          console.log('Свайп вниз');
        } else if (deltaY < -50) {
          console.log('Свайп вверх');
        }
      }
    }
  }
}
</script>

<style scoped>
.swipe-area {
  width: 300px;
  height: 200px;
  background-color: #e0e0ff;
  border: 1px solid #999;
  display: flex;
  align-items: center;
  justify-content: center;
  user-select: none;
}
</style>

Особенности обработки:

  • event.changedTouches содержит список касаний, завершившихся на данном элементе.
  • Используется разница координат (deltaX, deltaY) для определения направления свайпа.
  • Пороговое значение (например, 50 пикселей) предотвращает ложные срабатывания при случайных касаниях.

Многокасания и пинч-зум

Для обработки пинч-зума необходимо использовать два касания одновременно и вычислять расстояние между ними. Изменение этого расстояния позволяет определить увеличение или уменьшение масштаба.

Пример вычисления масштаба:

calculateDistance(touch1, touch2) {
  const dx = touch2.clientX - touch1.clientX;
  const dy = touch2.clientY - touch1.clientY;
  return Math.sqrt(dx * dx + dy * dy);
}

При изменении touchmove расстояние сравнивается с исходным, чтобы определить направление жеста:

  • Увеличение расстояния → увеличение масштаба.
  • Уменьшение расстояния → уменьшение масштаба.

Декларативные методы через пользовательские директивы

Vue позволяет создавать собственные директивы для работы с сенсорными событиями. Это удобно, если одни и те же жесты применяются к разным компонентам.

Пример директивы для свайпа:

app.directive('swipe', {
  beforeMount(el, binding) {
    let startX, startY;

    el.addEventListener('touchstart', (e) => {
      startX = e.touches[0].clientX;
      startY = e.touches[0].clientY;
    });

    el.addEventListener('touchend', (e) => {
      const endX = e.changedTouches[0].clientX;
      const endY = e.changedTouches[0].clientY;
      const deltaX = endX - startX;
      const deltaY = endY - startY;

      if (Math.abs(deltaX) > Math.abs(deltaY)) {
        if (deltaX > 50) binding.value('right');
        else if (deltaX < -50) binding.value('left');
      } else {
        if (deltaY > 50) binding.value('down');
        else if (deltaY < -50) binding.value('up');
      }
    });
  }
});

Использование:

<div v-swipe="onSwipe">Проведите пальцем</div>

<script>
export default {
  methods: {
    onSwipe(direction) {
      console.log('Свайп:', direction);
    }
  }
}
</script>

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

Взаимодействие с анимациями и переходами

Сенсорные события часто применяются для управления анимациями, например, свайп для закрытия карточки. Vue Transition API позволяет плавно менять состояние элемента при жестах, комбинируя события с v-if или v-show.

Пример использования с переходами:

<template>
  <transition name="slide">
    <div v-if="visible" v-swipe="handleSwipe" class="card">
      Карточка
    </div>
  </transition>
</template>

<script>
export default {
  data() {
    return { visible: true };
  },
  methods: {
    handleSwipe(direction) {
      if (direction === 'left') this.visible = false;
    }
  }
}
</script>

<style>
.slide-enter-active, .slide-leave-active {
  transition: transform 0.3s ease;
}
.slide-enter-from, .slide-leave-to {
  transform: translateX(100%);
}
</style>

Оптимизация производительности

  • Использовать passive-слушатели для touchmove и других событий, чтобы избежать блокировки скролла:
el.addEventListener('touchmove', handler, { passive: true });
  • Ограничивать количество вычислений в обработчиках touchmove, например, с помощью requestAnimationFrame.
  • Минимизировать количество DOM-операций во время жестов для плавности анимаций.

Сенсорные события и жесты во Vue.js позволяют создавать интерактивные мобильные интерфейсы, полностью управляемые через реактивные данные и методы компонентов. Директивы, вычисления координат и оптимизация производительности — ключевые инструменты для эффективной работы с сенсорными взаимодействиями.