What I want
… is to detect panning (or swiping) using Hammer.js in Angular 9.
It should should work like this:
- horizontal panning should be detected
- diagonal panning with certain degrees should be detected1
- vertical scrolling shoud not be prevented
- pinch to zoom should not be prevented as this will cause accessibility problems
In the image green
shows everything that is default by the browser and should not be prevented. blue
shows everything that should be prevented and be handled by Hammer.
Not sure what I mean? Take a look at Swiper
's demos. They work exactly like this.
1 It's clear how to detect the angle using event.angle
. But I'm not sure how to distinguish between preventing or not preventing the event if necessary.
What I've tried
… are all these things which I found on multipee questions on Stackoverflow and other blog articles:
import { BrowserModule, HammerModule, HammerGestureConfig, HAMMER_GESTURE_CONFIG } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import * as Hammer from 'hammerjs';
class MyHammerConfig extends HammerGestureConfig {
// Test #1
overrides = <any>{
swipe: { direction: hammer.DIRECTION_HORIZONTAL },
pinch: { enable: false },
rotate: { enable: false }
};
// Test #2
overrides = <any>{
swipe: { direction: Hammer.DIRECTION_ALL }
};
// Test #3
options = {
domEvents: true
}
// Test #4
buildHammer(element: HTMLElement) {
const mc = new Hammer(element, {
touchAction: 'pan-y'
});
return mc;
}
// Test #5
buildHammer(element: HTMLElement) {
const mc = new Hammer(element, {
touchAction: 'auto'
});
return mc;
}
}
@NgModule({
declarations: [
AppComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
HammerModule
],
providers: [
{
provide: Window,
useValue: window
},
{
provide: HAMMER_GESTURE_CONFIG,
useClass: MyHammerConfig
}
],
bootstrap: [
AppComponent
]
})
export class AppModule { }
All these things didn't work as they had different results and were not consistent through different devices (e.g. iPhone vs iPad):
- ✗ pinch to zoom did not work
- ✗ vertical scrolling did not work
- ✗ diagonal panning could not be detected
The current solution
… creates Hammer
directly in the component, like this:
import { Component, OnInit, ViewChild, ElementRef, OnDestroy, ViewChildren, QueryList } from '@angular/core';
import * as Hammer from 'hammerjs';
@Component({
selector: 'app-hero',
templateUrl: './hero.component.html',
styleUrls: ['./hero.component.scss']
})
export class HeroComponent implements OnInit, OnDestroy {
@ViewChild('list', { static: true }) list: ElementRef;
private hammertime: Hammer;
private isLocked: boolean = false;
ngOnInit() {
this.initHammer();
}
ngOnDestroy() {
// todo destroy hammertime
}
initHammer(): void {
this.hammertime = new Hammer(this.list.nativeElement, {touchAction : 'auto'});
this.hammertime.get('pan').set({ direction: Hammer.DIRECTION_HORIZONTAL, threshold: 60 });
this.hammertime.on('panleft panright', event => {
if (this.isLocked || event.maxPointers !== 1) {
return
};
this.goto(event.type === 'panLeft' ? 'next' : 'prev');
this.isLocked = true;
setTimeout(() => this.isLocked = false, 1000)
});
}
}
This does a lot of things:
- ✓ pinch to zoom works
- ✓ vertical scrolling works
- ✓ horizontal swipes are detected
But these things don't work or are not good:
- ✗ not the Angular way by not using the HammerModule
- ✗ no diagonal detection
- ✗
Hammer
-instancethis.hammertime
and attached events are never destroyed - ✗ A timeout is used to prevent the
pan
-event from firing multiple times - ✗ pointer count must be dectected manually
Questions?
- How can I make this work "the Angular way"?
- How can I detect the diagonal panning?
- How to correctly destroy
this.hammertime
when the goal is not possible "the Angular way"?