Hammer.js与React/Vue/Angular集成最佳实践

Hammer.js与React/Vue/Angular集成最佳实践

【免费下载链接】hammer.js A javascript library for multi-touch gestures :// You can touch this 【免费下载链接】hammer.js 项目地址: https://gitcode.com/gh_mirrors/ha/hammer.js

你是否在前端开发中遇到过触摸手势交互难题?是否想让Web应用在移动设备上拥有原生应用般的流畅体验?本文将带你一文掌握Hammer.js与三大主流前端框架(React/Vue/Angular)的集成技巧,读完你将获得:

  • 跨框架手势交互实现方案
  • 5分钟快速上手的集成模板
  • 解决手势冲突的最佳实践
  • 生产环境优化指南

什么是Hammer.js?

Hammer.js是一个轻量级的JavaScript库,专注于处理触摸设备上的手势识别。它支持点击(Tap)、双击(Double Tap)、长按(Press)、滑动(Swipe)、拖动(Pan)、缩放(Pinch)和旋转(Rotate)等多种手势,通过src/recognizers/目录下的模块化识别器实现精准的手势检测。

<!-- 国内CDN引入 -->
<script src="//cdn.jsdelivr.net/npm/hammerjs@2.0.8/hammer.min.js"></script>

核心概念快速了解

在开始集成前,需要了解Hammer.js的两个核心概念:

React集成方案

函数组件实现(推荐)

import { useRef, useEffect } from 'react';
import Hammer from 'hammerjs';

const GestureBox = () => {
  const boxRef = useRef(null);
  
  useEffect(() => {
    const hammer = new Hammer(boxRef.current);
    
    // 添加点击手势识别
    hammer.add(new Hammer.Tap({
      taps: 1,
      time: 250, // 最大按下时间,定义在[src/recognizers/tap.js#L119](https://link.gitcode.com/i/bf63ac959be1de911442901b13560b8d#L119)
      threshold: 9 // 允许的最小移动距离,定义在[src/recognizers/tap.js#L120](https://link.gitcode.com/i/bf63ac959be1de911442901b13560b8d#L120)
    }));
    
    // 绑定手势事件
    hammer.on('tap', (e) => {
      console.log('点击位置:', e.center);
      e.target.style.backgroundColor = '#4CAF50';
    });
    
    return () => {
      hammer.destroy(); // 组件卸载时清理
    };
  }, []);
  
  return (
    <div 
      ref={boxRef}
      style={{ width: '200px', height: '200px', backgroundColor: '#f0f0f0' }}
    >
      点击我
    </div>
  );
};

export default GestureBox;

自定义Hook封装

// useHammer.js
import { useRef, useEffect } from 'react';
import Hammer from 'hammerjs';

export function useHammer(options = {}) {
  const elementRef = useRef(null);
  const hammerRef = useRef(null);

  useEffect(() => {
    if (!elementRef.current) return;
    
    // 初始化Hammer管理器,参考[tests/unit/test_hammer.js#L56](https://link.gitcode.com/i/9cf55324a882523039f1dcb71a517b38)
    hammerRef.current = new Hammer.Manager(elementRef.current, {
      recognizers: options.recognizers || [[Hammer.Tap], [Hammer.Swipe]]
    });
    
    // 添加手势识别器
    if (options.gestures) {
      Object.entries(options.gestures).forEach(([name, config]) => {
        const Recognizer = Hammer[name];
        if (Recognizer) {
          hammerRef.current.add(new Recognizer(config));
        }
      });
    }
    
    // 绑定事件处理函数
    if (options.on) {
      Object.entries(options.on).forEach(([event, handler]) => {
        hammerRef.current.on(event, handler);
      });
    }

    return () => {
      hammerRef.current.destroy();
    };
  }, [options]);

  return elementRef;
}

// 使用示例
const SwipeBox = () => {
  const boxRef = useHammer({
    gestures: {
      Swipe: { direction: Hammer.DIRECTION_HORIZONTAL }
    },
    on: {
      swipeleft: () => alert('向左滑动'),
      swiperight: () => alert('向右滑动')
    }
  });

  return (
    <div 
      ref={boxRef}
      style={{ width: '100%', height: '150px', backgroundColor: '#e3f2fd' }}
    >
      左右滑动我
    </div>
  );
};

Vue集成方案

Vue 3组合式API实现

<template>
  <div ref="gestureArea" class="gesture-area">
    <p>{{ message }}</p>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import Hammer from 'hammerjs';

const gestureArea = ref(null);
const message = ref('点击或拖动我');
let hammer = null;

onMounted(() => {
  if (!gestureArea.value) return;
  
  // 初始化Hammer实例
  hammer = new Hammer(gestureArea.value);
  
  // 添加拖动识别器,参考[src/recognizers/pan.js](https://link.gitcode.com/i/8a1d4ddb5b4609aa8154cb44e66cb351)
  const pan = new Hammer.Pan({
    direction: Hammer.DIRECTION_ALL,
    threshold: 10
  });
  
  // 添加长按识别器,参考[src/recognizers/press.js](https://link.gitcode.com/i/af06c71666ebea70bb0cc0bc13cdbf5c)
  const press = new Hammer.Press({
    time: 500, // 长按触发时间
    threshold: 5
  });
  
  // 同时识别拖动和长按,参考[tests/unit/test_hammer.js#L145](https://link.gitcode.com/i/064862f13ba4bebe603171c6a6c3b5ab)
  hammer.add([pan, press]);
  
  // 拖动事件
  hammer.on('pan', (e) => {
    message.value = `拖动中: X: ${e.deltaX}, Y: ${e.deltaY}`;
    e.target.style.transform = `translate(${e.deltaX}px, ${e.deltaY}px)`;
  });
  
  // 长按事件
  hammer.on('press', () => {
    message.value = '长按触发!';
    gestureArea.value.style.backgroundColor = '#ff9800';
  });
});

onUnmounted(() => {
  if (hammer) {
    hammer.destroy(); // 清理资源
  }
});
</script>

<style scoped>
.gesture-area {
  width: 300px;
  height: 200px;
  border: 2px solid #333;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color 0.3s;
}
</style>

Vue指令封装

// directives/hammer.js
import Hammer from 'hammerjs';

export default {
  mounted(el, binding) {
    const { value } = binding;
    if (typeof value !== 'object') return;
    
    // 创建Hammer实例
    const hammer = new Hammer.Manager(el);
    
    // 存储实例便于卸载时清理
    el._hammer = hammer;
    
    // 添加手势识别器
    if (value.gestures) {
      Object.entries(value.gestures).forEach(([name, options]) => {
        const Recognizer = Hammer[name];
        if (Recognizer) {
          hammer.add(new Recognizer(options));
        }
      });
    }
    
    // 绑定事件
    if (value.on) {
      Object.entries(value.on).forEach(([event, handler]) => {
        hammer.on(event, handler);
      });
    }
  },
  
  unmounted(el) {
    if (el._hammer) {
      el._hammer.destroy();
      delete el._hammer;
    }
  }
};

// main.js中注册
import { createApp } from 'vue';
import App from './App.vue';
import hammerDirective from './directives/hammer';

const app = createApp(App);
app.directive('hammer', hammerDirective);
app.mount('#app');

使用指令:

<template>
  <div v-hammer="hammerOptions" class="gesture-box">
    使用指令的手势区域
  </div>
</template>

<script setup>
const hammerOptions = {
  gestures: {
    Tap: { taps: 2 },
    Swipe: { direction: Hammer.DIRECTION_LEFT }
  },
  on: {
    doubletap: () => alert('双击了!'),
    swipeleft: () => alert('向左滑动!')
  }
};
</script>

Angular集成方案

组件实现

// gesture.component.ts
import { Component, ElementRef, ViewChild, OnInit, OnDestroy } from '@angular/core';
import Hammer from 'hammerjs';

@Component({
  selector: 'app-gesture',
  template: `
    <div #gestureArea class="gesture-area">
      <p>{{ status }}</p>
      <div [style.transform]="transform" class="movable-box"></div>
    </div>
  `,
  styles: [`
    .gesture-area {
      width: 400px;
      height: 300px;
      border: 1px solid #ccc;
      position: relative;
    }
    .movable-box {
      width: 50px;
      height: 50px;
      background-color: #2196F3;
      position: absolute;
      top: 125px;
      left: 175px;
    }
  `]
})
export class GestureComponent implements OnInit, OnDestroy {
  @ViewChild('gestureArea') gestureArea!: ElementRef;
  status = '准备就绪';
  transform = 'translate(0, 0)';
  private hammer!: Hammer.Manager;
  private position = { x: 175, y: 125 };

  ngOnInit() {
    // 初始化Hammer,参考[tests/unit/test_hammer.js#L140](https://link.gitcode.com/i/7610a5c90342b185fde29a1d0435f9eb)
    this.hammer = new Hammer.Manager(this.gestureArea.nativeElement, {
      recognizers: [
        [Hammer.Pan, { direction: Hammer.DIRECTION_ALL }],
        [Hammer.Pinch],
        [Hammer.Rotate]
      ]
    });

    // 允许多个手势同时识别,参考[src/recognizerjs/recognizer-constructor.js](https://link.gitcode.com/i/976169fd3c595ff57196bc22ac9c3ff2)
    this.hammer.get('pan').recognizeWith(['pinch', 'rotate']);
    
    // 平移事件
    this.hammer.on('pan', (e) => {
      this.position.x += e.deltaX;
      this.position.y += e.deltaY;
      this.transform = `translate(${this.position.x}px, ${this.position.y}px)`;
      this.status = `位置: (${Math.round(this.position.x)}, ${Math.round(this.position.y)})`;
    });
    
    // 缩放手势
    this.hammer.on('pinch', (e) => {
      const scale = e.scale;
      this.status = `缩放: ${scale.toFixed(2)}`;
    });
    
    // 旋转手势
    this.hammer.on('rotate', (e) => {
      const angle = e.rotation;
      this.status = `旋转: ${angle.toFixed(1)}°`;
    });
  }

  ngOnDestroy() {
    if (this.hammer) {
      this.hammer.destroy(); // 清理
    }
  }
}

指令实现

// hammer.directive.ts
import { Directive, ElementRef, Input, OnInit, OnDestroy } from '@angular/core';
import Hammer from 'hammerjs';

@Directive({
  selector: '[appHammer]'
})
export class HammerDirective implements OnInit, OnDestroy {
  @Input() appHammer!: {
    gestures?: Record<string, any>;
    on?: Record<string, (e: any) => void>;
  };
  
  private hammer!: Hammer.Manager;

  constructor(private el: ElementRef) {}

  ngOnInit() {
    const options = this.appHammer || {};
    
    // 初始化Hammer管理器
    this.hammer = new Hammer.Manager(this.el.nativeElement);
    
    // 添加手势识别器
    if (options.gestures) {
      Object.entries(options.gestures).forEach(([name, config]) => {
        const Recognizer = Hammer[name as keyof typeof Hammer];
        if (Recognizer) {
          this.hammer.add(new Recognizer(config));
        }
      });
    }
    
    // 绑定事件处理函数
    if (options.on) {
      Object.entries(options.on).forEach(([event, handler]) => {
        this.hammer.on(event, handler);
      });
    }
  }

  ngOnDestroy() {
    if (this.hammer) {
      this.hammer.destroy();
    }
  }
}

使用指令:

<div [appHammer]="{
  gestures: {
    Tap: { taps: 1 },
    Press: { time: 1000 }
  },
  on: {
    tap: (e) => console.log('点击了', e),
    press: (e) => console.log('长按了', e)
  }
}" class="example-area">
  指令手势区域
</div>

跨框架通用最佳实践

1. 手势冲突解决方案

当多个手势同时作用于同一元素时,可能会产生冲突。解决方法:

// 设置识别器依赖关系,参考[src/recognizerjs/recognizer-constructor.js](https://link.gitcode.com/i/976169fd3c595ff57196bc22ac9c3ff2)
const pan = new Hammer.Pan();
const swipe = new Hammer.Swipe();

// 先尝试识别swipe,如果失败再识别pan
pan.requireFailure(swipe);

// 或者同时识别多个手势
pan.recognizeWith(swipe);

2. 性能优化

  • 事件委托:避免为多个元素单独创建Hammer实例
  • 手势节流:对高频触发的事件进行节流处理
  • 及时销毁:在组件卸载时调用hammer.destroy()释放资源
// 性能优化示例
function optimizeHammer(hammer) {
  // 降低事件触发频率
  hammer.get('pan').options.enable = false;
  
  // 按需启用
  hammer.on('press', () => {
    hammer.get('pan').options.enable = true;
  });
  
  // 离开时禁用
  hammer.on('pressup', () => {
    hammer.get('pan').options.enable = false;
  });
}

3. 移动优先设计

使用Hammer.js的触摸动作配置src/touchactionjs/,优化移动设备体验:

// 优化触摸行为
const hammer = new Hammer(element, {
  touchAction: 'manipulation' // 优化触摸操作,参考[src/touchactionjs/touchaction-Consts.js](https://link.gitcode.com/i/05ba5e6813d1e443d77a28ac9347fab3)
});

总结

Hammer.js提供了强大的手势识别能力,通过本文介绍的方法,可以轻松集成到React、Vue和Angular项目中。关键要点:

  1. 初始化:根据框架特性选择合适的初始化时机(React的useEffect、Vue的onMounted、Angular的ngOnInit)
  2. 清理:组件卸载时务必调用destroy()方法释放资源
  3. 定制:通过src/recognizers/目录下的识别器自定义手势行为
  4. 优化:合理设置识别器依赖关系,避免手势冲突

无论你使用哪个前端框架,Hammer.js都能帮助你快速实现丰富的触摸交互效果,提升移动用户体验。现在就尝试将这些技巧应用到你的项目中吧!

【免费下载链接】hammer.js A javascript library for multi-touch gestures :// You can touch this 【免费下载链接】hammer.js 项目地址: https://gitcode.com/gh_mirrors/ha/hammer.js

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值