Search code examples
angularangular-material2ngrxgesturehammer.js

Capture swipe event on entire screen with Hammer and Angular 8


I'm trying to capture swipe gestures on the entire screen of my application. The idea is to have these dispatch actions that other services can listen to, in this case a router for navigation purposes.

Because I only want these listeners in one place I tried attaching them to the body or to a div covering the screen of my app. Both methods don't work as I want.

<div (swipeleft)="swipeLeft()" (swiperight)="swipeRight()" class="touch"></div>

swipeLeft() {
  this.store.dispatch(UserActions.swipeLeft());
}

The problem with a touch layer should have been obvious in retrospect: it covers the screen and thus the rest of the app. Setting pointer-events: none; to reach the rest of the app breaks the swipe detection.

const mc = new Hammer(document.querySelector('body'));

mc.on('swipeleft swiperight', (ev) => {
  console.log(ev.type);
  // this.store.dispatch(UserActions.swipeLeft());
});

The problem with attaching it here is that it only seems to register swipes on certain elements like app-root and a status-bar that I have but not on my router-outlet nor a toolbar in the bottom that holds some buttons.

So, how do I capture swipes on my entire application?

I've tried to create a snippet to reproduce the problem, but without Angular components it pretty much behaves as I hoped my app would. Similar behavior as my problem can be observed though when trying to swipe from around the button margins in the snippet.

const touchLayer = document.getElementById('touch');
const body = document.querySelector('body');

const mc = new Hammer(body);

mc.on("swipeleft swiperight", function(ev) {
  touchLayer.textContent = ev.type + " gesture detected.";
});
#touch,
#myApp {
  position: fixed;
  height: 300px;
  width: 100%;
}

#myApp {
  background: repeating-linear-gradient(-55deg, #666, #666 10px, #333 10px, #333 20px);
  opacity: .5;
}

#touch {
  background: cyan;
  text-align: center;
  font: 30px/300px Helvetica, Arial, sans-serif;
  opacity: .5;
}

.card {
  width: 200px;
  height: 100px;
  background: red;
  position: relative;
  margin: 1em;
}

button {
  margin: 2em;
  right: 0;
  bottom: 0;
  position: absolute;
}
<script src="https://hammerjs.github.io/dist/hammer.js"></script>

<!-- Tried putting this on top, blocking the screen -->
<div id="touch"></div>

<div id="myApp">
  <div class="card">
    <button onclick="alert('test')">Button!</button>
  </div>
  <div class="card">
    <button onclick="alert('test')">Button!</button>
  </div>
</div>


Solution

  • When you want to add Hammer to an Angular 8 application, you need as provider HAMMER_GESTURE_CONFIG and use class HammerGestureConfig

      providers: [
        {
          provide: HAMMER_GESTURE_CONFIG,
          useClass: HammerGestureConfig
        }
      ]
    

    Well, if you want use another class thar override some propertie -e.g. you want to swipe in all direction-, you create a class that extends HammerGestureConfig and use this class in the provider

    export class MyHammerConfig extends HammerGestureConfig {
        overrides = <any>{
          swipe: { direction: Hammer.DIRECTION_ALL  }
        };
    }
    
     // And use as provider
    
      providers: [
        {
          provide: HAMMER_GESTURE_CONFIG,
          useClass: MyHammerConfig
        }
      ]
    

    In your app, you can use fromEvent rxjs

    const hammerConfig = new HammerGestureConfig()
    //or if you use another class as provider:
    //    const hammerConfig=new MyHammerConfig()
    
    const hammer=hammerConfig.buildHammer(document.documentElement)
    fromEvent(hammer, "swipe").pipe(
      takeWhile(()=>this.alive))
      .subscribe((res: any) => {
        console.log(res.deltaX);
    });
    

    see an example in stackblitz

    (*)I put a pipe takeWhile to remark the necesary unsubscribe