阿里云主机折上折
  • 微信号
您当前的位置:网站首页 > 触摸事件与手势识别

触摸事件与手势识别

作者:陈川 阅读数:32753人阅读 分类: HTML

触摸事件基础

HTML5为移动设备提供了丰富的触摸事件接口,使开发者能够创建响应式的触控交互体验。触摸事件与传统鼠标事件不同,可以同时跟踪多个触控点,实现复杂的手势操作。

// 基本触摸事件监听示例
const element = document.getElementById('touchArea');

element.addEventListener('touchstart', (e) => {
  console.log('触摸开始', e.touches);
});

element.addEventListener('touchmove', (e) => {
  e.preventDefault(); // 阻止默认滚动行为
  console.log('触摸移动', e.touches);
});

element.addEventListener('touchend', (e) => {
  console.log('触摸结束', e.changedTouches);
});

触摸事件主要包括:

  • touchstart:手指触摸屏幕时触发
  • touchmove:手指在屏幕上滑动时触发
  • touchend:手指离开屏幕时触发
  • touchcancel:系统取消触摸时触发(如来电打断)

多点触控处理

多点触控是移动设备的重要特性,通过TouchEvent对象的touches属性可以获取所有当前触摸点的信息。

element.addEventListener('touchmove', (e) => {
  const touches = e.touches;
  if (touches.length === 2) {
    // 计算两点间距离
    const dx = touches[0].clientX - touches[1].clientX;
    const dy = touches[0].clientY - touches[1].clientY;
    const distance = Math.sqrt(dx * dx + dy * dy);
    console.log('两点距离:', distance);
  }
});

每个触摸点包含以下常用属性:

  • clientX/clientY:相对于视口的坐标
  • pageX/pageY:相对于文档的坐标
  • identifier:触摸点唯一标识符
  • target:最初触摸的DOM元素

常见手势识别

点击与长按

let touchTimer;
let isLongPress = false;

element.addEventListener('touchstart', () => {
  touchTimer = setTimeout(() => {
    isLongPress = true;
    console.log('长按事件');
  }, 500); // 500毫秒判定为长按
});

element.addEventListener('touchend', (e) => {
  clearTimeout(touchTimer);
  if (!isLongPress) {
    console.log('点击事件');
  }
  isLongPress = false;
});

滑动识别

let startX, startY;

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

element.addEventListener('touchmove', (e) => {
  const deltaX = e.touches[0].clientX - startX;
  const deltaY = e.touches[0].clientY - startY;
  
  if (Math.abs(deltaX) > Math.abs(deltaY)) {
    if (deltaX > 10) {
      console.log('向右滑动');
    } else if (deltaX < -10) {
      console.log('向左滑动');
    }
  } else {
    if (deltaY > 10) {
      console.log('向下滑动');
    } else if (deltaY < -10) {
      console.log('向上滑动');
    }
  }
});

缩放手势

let initialDistance = 0;

element.addEventListener('touchstart', (e) => {
  if (e.touches.length === 2) {
    const dx = e.touches[0].clientX - e.touches[1].clientX;
    const dy = e.touches[0].clientY - e.touches[1].clientY;
    initialDistance = Math.sqrt(dx * dx + dy * dy);
  }
});

element.addEventListener('touchmove', (e) => {
  if (e.touches.length === 2) {
    const dx = e.touches[0].clientX - e.touches[1].clientX;
    const dy = e.touches[0].clientY - e.touches[1].clientY;
    const currentDistance = Math.sqrt(dx * dx + dy * dy);
    
    if (initialDistance > 0) {
      const scale = currentDistance / initialDistance;
      console.log('缩放比例:', scale.toFixed(2));
    }
  }
});

性能优化与最佳实践

事件节流

let lastMoveTime = 0;
const moveThreshold = 16; // 约60fps

element.addEventListener('touchmove', (e) => {
  const now = Date.now();
  if (now - lastMoveTime >= moveThreshold) {
    // 执行操作
    updatePosition(e.touches[0].clientX, e.touches[0].clientY);
    lastMoveTime = now;
  }
});

被动事件监听器

// 改善滚动性能
element.addEventListener('touchmove', (e) => {
  // 轻量级操作
}, { passive: true });

CSS硬件加速

.touch-element {
  will-change: transform; /* 提示浏览器可能的变化 */
  transform: translateZ(0); /* 触发硬件加速 */
}

手势库的应用

虽然可以手动实现手势识别,但使用专业库能提高开发效率和稳定性。Hammer.js是流行的选择:

import Hammer from 'hammerjs';

const mc = new Hammer(element);
mc.get('pan').set({ direction: Hammer.DIRECTION_ALL });

mc.on('pan', (e) => {
  console.log('平移:', e.deltaX, e.deltaY);
});

mc.on('pinch', (e) => {
  console.log('缩放:', e.scale);
});

mc.on('rotate', (e) => {
  console.log('旋转:', e.rotation);
});

跨平台兼容性处理

不同设备和浏览器对触摸事件的支持存在差异,需要做好兼容处理:

function getSupportedEvents() {
  if ('ontouchstart' in window) {
    return {
      start: 'touchstart',
      move: 'touchmove',
      end: 'touchend',
      cancel: 'touchcancel'
    };
  } else if ('onpointerdown' in window) {
    return {
      start: 'pointerdown',
      move: 'pointermove',
      end: 'pointerup',
      cancel: 'pointercancel'
    };
  } else {
    return {
      start: 'mousedown',
      move: 'mousemove',
      end: 'mouseup',
      cancel: null
    };
  }
}

const events = getSupportedEvents();
element.addEventListener(events.start, handleStart);

高级手势组合

复杂手势通常需要状态管理:

class GestureRecognizer {
  constructor(element) {
    this.state = 'idle';
    this.element = element;
    this.setupEvents();
  }
  
  setupEvents() {
    this.element.addEventListener('touchstart', this.handleStart.bind(this));
    this.element.addEventListener('touchmove', this.handleMove.bind(this));
    this.element.addEventListener('touchend', this.handleEnd.bind(this));
  }
  
  handleStart(e) {
    if (e.touches.length === 2) {
      this.state = 'potentialZoom';
      this.initialDistance = this.getTouchDistance(e.touches);
    } else {
      this.state = 'potentialTap';
      this.startTime = Date.now();
    }
  }
  
  handleMove(e) {
    if (this.state === 'potentialZoom' && e.touches.length === 2) {
      const currentDistance = this.getTouchDistance(e.touches);
      const scale = currentDistance / this.initialDistance;
      this.dispatchEvent('zoom', { scale });
    }
  }
  
  handleEnd(e) {
    if (this.state === 'potentialTap' && Date.now() - this.startTime < 200) {
      this.dispatchEvent('tap');
    }
    this.state = 'idle';
  }
  
  getTouchDistance(touches) {
    const dx = touches[0].clientX - touches[1].clientX;
    const dy = touches[0].clientY - touches[1].clientY;
    return Math.sqrt(dx * dx + dy * dy);
  }
  
  dispatchEvent(type, detail) {
    const event = new CustomEvent(`gesture:${type}`, { detail });
    this.element.dispatchEvent(event);
  }
}

实际应用场景

图片查看器

class ImageViewer {
  constructor(container) {
    this.container = container;
    this.image = container.querySelector('img');
    this.scale = 1;
    this.position = { x: 0, y: 0 };
    this.setupGestures();
  }
  
  setupGestures() {
    const mc = new Hammer(this.container);
    
    // 双击恢复原始大小
    mc.on('doubletap', () => {
      this.resetTransform();
    });
    
    // 平移
    mc.on('pan', (e) => {
      this.position.x += e.deltaX;
      this.position.y += e.deltaY;
      this.updateTransform();
    });
    
    // 缩放
    mc.on('pinch', (e) => {
      this.scale *= e.scale;
      this.updateTransform();
    });
  }
  
  updateTransform() {
    this.image.style.transform = `
      translate(${this.position.x}px, ${this.position.y}px)
      scale(${this.scale})
    `;
  }
  
  resetTransform() {
    this.scale = 1;
    this.position = { x: 0, y: 0 };
    this.updateTransform();
  }
}

滑动菜单

class SlideMenu {
  constructor(menuElement) {
    this.menu = menuElement;
    this.startX = 0;
    this.currentX = 0;
    this.threshold = 100;
    this.setupTouchEvents();
  }
  
  setupTouchEvents() {
    this.menu.addEventListener('touchstart', (e) => {
      this.startX = e.touches[0].clientX;
    });
    
    this.menu.addEventListener('touchmove', (e) => {
      this.currentX = e.touches[0].clientX - this.startX;
      // 限制向右滑动
      if (this.currentX > 0) {
        this.menu.style.transform = `translateX(${this.currentX}px)`;
      }
    });
    
    this.menu.addEventListener('touchend', () => {
      if (this.currentX > this.threshold) {
        this.openMenu();
      } else {
        this.closeMenu();
      }
    });
  }
  
  openMenu() {
    this.menu.style.transform = 'translateX(250px)';
  }
  
  closeMenu() {
    this.menu.style.transform = 'translateX(0)';
  }
}

本站部分内容来自互联网,一切版权均归源网站或源作者所有。

如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn

前端川

前端川,陈川的代码茶馆🍵,专治各种不服的Bug退散符💻,日常贩卖秃头警告级的开发心得🛠️,附赠一行代码笑十年的摸鱼宝典🐟,偶尔掉落咖啡杯里泡开的像素级浪漫☕。‌