在Angular2中使用移动事件

12
我想知道如何在移动设备上绑定事件,具体来说是想在Angular 2中将函数与滑动事件绑定。我在this issue on Github 上看到,Angular 2使用Hammer.js处理移动事件。
但是我遇到了一些问题,因为我收到了以下错误信息:

EXCEPTION: Hammer.js未加载,无法绑定swipeleft事件

以下是我的一部分代码:
import {Component, View, AfterContentInit} from 'angular2/core';
import {HelperService} from "./helper-service";
import {HammerGesturesPluginCommon} from 'angular2/src/platform/dom/events/hammer_common'

@View({
  template: `<div [id]="middleCircle" (swipeleft)="doThis()"></div>`
})

export class ColumnDirective implements AfterContentInit {
  constructor(private helperService:HelperService) {}
  doThis(){
     console.log('This thing has been done.');
   }
 }

如果我在构造函数中添加 Hammer 手势,我会收到以下错误提示:
constructor(private helperService:HelperService, private hammerGesturesPluginCommon: HammerGesturesPluginCommon) {}

异常:没有t的提供程序!(ColumnDirective -> t)

感谢您对此问题的任何帮助!


我刚发现这个帖子,我正在尝试实现同样的事情。如果我发现了什么,我会告诉你们的。 - Billy Mayes
我通过在我的index.html中为hammer.js添加一个脚本标签(我正在使用Angular 2种子项目)来解决了“Hammer.js未加载”的问题,但是当我触发一个滑动操作时,我得到了一长串错误,起始于“EXCEPTION: RangeError: Maximum call stack size exceeded”。 - Billy Mayes
是的,@BillyMayes,现在有同样的问题。 - Eric Gonzalo
嘿@EricGonzalo,你最终解决了这个问题吗?我在当前的Angular2(master)中没有从Hammer获取任何事件。 - wannabeartist
1
@wannabeartist,是的,按照下面Billy Mayes的回答基本上解决了问题。不使用Angular2,而是使用hammer.js设置更好。虽然我必须将我的hammer.js函数放置在setTimeout中,因为我在AfterContentInit中设置了我的内容,但我也没有在组件上进行hammerInitialized检查。 - Eric Gonzalo
6个回答

14

经过反复尝试,我未能成功让Angular2的HammerGesturesPluginCommon工作(到目前为止)。受Bill Mayes答案的启发,我将此作为他答案的延伸提交,我已经成功运行了它(在iPad mini和Android手机上测试过)。

基本上,我的解决方案如下。

首先,在index.html文件的script标签中手动引用hammer.js(我还引用了hammer-time来消除300毫秒的延迟):

其次,安装hammerjs的类型定义(tsd install hammerjs-save)。然后,您可以创建一个像这样的Angular2属性指令:

/// <reference path="./../../../typings/hammerjs/hammerjs.d.ts" />
import {Directive, ElementRef, AfterViewInit, Output, EventEmitter} from 'angular2/core';
@Directive({
    selector: '[hammer-gestures]'
})
export class HammerGesturesDirective implements AfterViewInit {
    @Output() onGesture = new EventEmitter();
    static hammerInitialized = false;
    constructor(private el: ElementRef) {

    }
    ngAfterViewInit() {

        if (!HammerGesturesDirective.hammerInitialized) {

            let hammertime = new Hammer(this.el.nativeElement);
            hammertime.get('swipe').set({ direction: Hammer.DIRECTION_ALL });
            hammertime.on("swipeup", (ev) => {
                this.onGesture.emit("swipeup");
            });
            hammertime.on("swipedown", (ev) => {
                this.onGesture.emit("swipedown");
            });
            hammertime.on("swipeleft", (ev) => {
                this.onGesture.emit("swipeleft");
            });
            hammertime.on("swiperight", (ev) => {
                this.onGesture.emit("swiperight");
            });
            hammertime.on("tap", (ev) => {
                this.onGesture.emit("tap");
            });

            HammerGesturesDirective.hammerInitialized = true;
        }


    }
}

如果您想检测垂直(上下)滑动(左右滑动为默认值,非垂直),则需要设置Hammer.DIRECTION_ALL。有关hammer api的更多选项可以在此处找到:http://hammerjs.github.io/api/#hammer

最后,在您的父组件中,您可以这样做:

import {Component} from "angular2/core";
import {HammerGesturesDirective} from "./path/HammerGesturesDirective";

    @Component({
        selector: "my-ng2-component",
        template: `<div style='width:100px; height: 100px;background-color: red' 
            (onGesture)="doSwipe($event)" hammer-gestures></div>`,
        directives: [HammerGesturesDirective]

    })
    export class MyNg2Component {
        constructor() { }

        doSwipe(direction: string) {
            alert(direction);
        }

    }

这种方式只需要在想要启用 hammer 手势的特定元素上引用属性hammer-gestures即可。 注意:该元素似乎需要一个唯一的 id 才能正常工作


2
我还没有测试过这个,但我非常喜欢这个想法,以至于我在办公室大声说了“酷”。它看起来很干净。 - Sam Vloeberghs
很好的解释!+1 - Pardeep Jain
1
在最新版本的Angular2中,我不认为directives仍然存在于@Component。相反,我必须将指令添加到应用程序的@NgModule中的declarations中。 - twiz
解决方案在 Android 设备上进行了测试(使用 Ionic 2 框架),它可以正常工作。 - g3k0
@brando,感谢你的解决方案。我在滑动时从一个组件导航到另一个组件。导航后,需要重新加载页面才能使滑动功能生效。请帮忙修复这个问题。 - kamalav
显示剩余2条评论

8

虽然已经过了很长时间,但如果您只有简单的要求,不想使用hammer.js,那么这里有一个基本的水平滑动器。只需将其放入组件中,并添加您自己的doSwipeLeftdoSwipeRight函数即可。

  touch1 = {x:0,y:0,time:0};

  @HostListener('touchstart', ['$event'])
  @HostListener('touchend', ['$event'])
  //@HostListener('touchmove', ['$event'])
  @HostListener('touchcancel', ['$event'])
  handleTouch(ev){
    var touch = ev.touches[0] || ev.changedTouches[0];
    if (ev.type === 'touchstart'){
      this.touch1.x = touch.pageX;
      this.touch1.y = touch.pageY;
      this.touch1.time = ev.timeStamp;
    } else if (ev.type === 'touchend'){
      var dx = touch.pageX - this.touch1.x;
      var dy = touch.pageY - this.touch1.y;
      var dt = ev.timeStamp - this.touch1.time;

      if (dt < 500){
        // swipe lasted less than 500 ms
        if (Math.abs(dx) > 60){
          // delta x is at least 60 pixels
          if (dx > 0){
            this.doSwipeLeft(ev);
          } else {
            this.doSwipeRight(ev);
          }
        }
      }
    } 
  }

3

我有些犹豫是否要在我的应用程序中添加滑动手势,因为这里的答案似乎表明这会有些混乱。

异常:Hammer.js未加载,无法绑定swipeleft事件

通过简单地加载hammer.js即可轻松处理此错误。不需要自定义代码。

评论中提到的另一个错误似乎是已经在rc1中修复的漏洞。


2
我通过绕过内置的 Hammer 集成并运行自己的集成,成功地得到了一些东西:
import { Component, ElementRef, AfterViewInit } from 'angular2/core';

@Component({
  selector: 'redeem',
  templateUrl: './redeem/components/redeem.html'
})
export class RedeemCmp implements AfterViewInit {

    static hammerInitialized = false;

    constructor(private el:ElementRef) { }

    ngAfterViewInit() {
        console.log('in ngAfterViewInit');
        if (!RedeemCmp.hammerInitialized) {
            console.log('hammer not initialised');

            var myElement = document.getElementById('redeemwrap');
            var hammertime = new Hammer(myElement);
            hammertime.on('swiperight', function(ev) {
                console.log('caught swipe right');
                console.log(ev);
            });

            RedeemCmp.hammerInitialized = true;
        } else {            
            console.log('hammer already initialised');
        }
    }
}

那个看起来是一个好主意。因为我们还在测试阶段,很多东西仍然存在问题。感谢你的帮助! - Eric Gonzalo
使用RC1版本时,它在var hammertime = new Hammer(myElement);这一行给我报错,提示“Hammer未定义”。有任何想法是RC1中发生了什么变化吗? - LorDex

2
"

异常: 抛出“EXCEPTION: Hammer.js未加载,无法绑定swipeleft事件”,是因为需要将hammerjs包含在页面中。

示例: <script src="node_modules/hammerjs/hammer.js"></script>

我已经阅读了所有关于如何在angular中使用hammerjs的其他答案,它们看起来都很hacky。 默认情况下,HammerJs禁用了SwipeDown和SwipeUp方法。所有之前的回答都不是解决手势问题或覆盖hammer的默认设置的正确的angular2 typescript方式。 这让我花费了约4个小时来深入研究angular2和hammerjs的提交。

首先,我们需要为angular2获取hammerjs类型定义。

"
typings install github:DefinitelyTyped/DefinitelyTyped/hammerjs/hammerjs.d.ts#de8e80dfe5360fef44d00c41257d5ef37add000a --global --save

下一步,我们需要创建自定义的覆盖配置类。这个类可以用来覆盖所有默认的 hammerjs 和 angular2 hammerjs 配置设置。
hammer.config.ts:
import { HammerGestureConfig } from '@angular/platform-browser';
import {HammerInstance} from "@angular/platform-browser/src/dom/events/hammer_gestures";


export class HammerConfig extends HammerGestureConfig  {

    buildHammer(element: HTMLElement): HammerInstance {
        var mc = new Hammer(element);

        mc.get('pinch').set({ enable: true });
        mc.get('rotate').set({ enable: true });

        mc.add( new Hammer.Swipe({ direction: Hammer.DIRECTION_ALL, threshold: 0 }) );

        for (let eventName in this.overrides) {
            mc.get(eventName).set(this.overrides[eventName]);
        }

        return mc;
    }
}

我们需要做的最后一件事是打开我们的主 Bootstrap 文件,并将我们的自定义配置插入到 bootstrap 方法中,如下所示:
// ... other imports ...
import { HammerConfig } from './shared/hammer.config';

bootstrap(AppComponent, [
    provide(HAMMER_GESTURE_CONFIG, { useClass: HammerConfig })
]);

现在在我们的应用程序中,我们可以使用swipeDown和swipeUp。
<div class="some-block" (swipeDown)="onSwipeDown"></div>

这个 pull request 让我能够覆盖默认设置,并为我找到正确的方法提供了一个起点。你可以在下面的链接中查看更多信息:https://github.com/angular/angular/pull/7924

0

您需要将HelperServiceHammerGesturesPluginCommon添加到提供程序中的某个位置,可以在@Component(...)注释或bootstrap(AppComponent, [...])中添加,以消除错误“没有提供程序...”

您是否在入口页面的某个位置添加了Hammer.js的脚本标签?


我在我的 @Component 中添加了 providers: [HammerGesturesPluginCommon],并从 cdn 包含了 Hammer.js 的脚本标签,现在我得到了“最大调用堆栈大小超出”的错误,看起来我们离成功更近了! - Eric Gonzalo
也许 https://github.com/angular/angular/issues/5964 或 https://github.com/angular/angular/issues/5964 可以提供一些信息。我记得的是不要显式添加 zone.js(我自己没有遇到这个问题,只是记得看到过讨论,而且我不知道这是否仍然存在)。也可能是脚本标签的顺序问题。 - Günter Zöchbauer

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接