Demo 1

Try it...

Installation

npm

npm i -D amstramgram-swipe-observer

And then in your javascript :

import SwipeObserver from 'Amstramgram-Swipe-Observer'

Manually

Just download the source and use it in your project.

import SwipeObserver from './src_folder/amstramgramSwipeObserver'

Usage

//Observe swipe events on the container with a swipe class
const swipeObserver = new SwipeObserver(document.querySelector('.swipe'))

//Add an event listener to the swipe event
swipeObserver.addEventListener('swipe', (e) => {
  console.log('SWIPE', e.detail)
})

//Add an event listener to the swiping event
swipeObserver.addEventListener('swiping', (e) => {
  console.log('SWIPING', e.detail)
})

//Add an event listener to the cancel event
//This event is dispatched when the horizontal and vertical distances 
//between the start and the end of the movement 
//are less than the value set by the threshold option
swipeObserver.addEventListener('cancel', (e) => {
  console.log('SWIPE CANCELLED', e.detail)
})

//Activate the observer
swipeObserver.on()

Event detail

The detail property of the swiping, swipe and cancel events contains some usefull informations :

client

An object with four properties :
· x0 : clientX value of the starting event.
· y0 : clientY value of the starting event.
· x1 : clientX value of the current event.
· y1 : clientY value of the current event.

delta

An object with two properties :
· x : Horizontal distance between starting and current point for swipe and cancel events or between previous and current point for a swiping event.
· y : Vertical distance between starting and current point for swipe and cancel events or between previous and current point for a swiping event.

direction

A string indicating the direction of the movement : "left", "right", "up", "down" or an empty string "".

duration

A integer indicating in milliseconds the time elapsed since the beginning of the movement.

events

An object with two properties :
· start : the original event.
· end : the current event.

orientation

A string indicating the orientation of the movement : "hor" (for horizontal), "vert" (for vertical) or an empty string "".

page

An object with four properties :
· x0 : pageX value of the starting event.
· y0 : pageY value of the starting event.
· x1 : pageX value of the current event.
· y1 : pageY value of the current event.

pointerType

A string indicating the type of pointer used : "mouse", "touch" or "pen".

API

constructor

/**
* @param {HTMLElement} el : element to observe
* @param {Object} [opt = {}]
*    @property {Boolean} [active = false] - If true, activate the swipe listener
*    @property {Integer} [threshold = 20] - Minimum distance in pixels 
*              between the start and the end of the movement to detect a swipe
*/
constructor(el, opt = {})
 
//Example
const swipeObserver = new SwipeObserver(document.querySelector('.swipe'), { active: true })

on

/**
* @description : Activate the swipe listener and/or update the options
* @param {Object} [opt = {}]
*    @property {Integer} [threshold = 20] - Minimum distance in pixels 
*              between the start and the end of the movement to detect a swipe
*/
on(opt = {})

//Example
//Initialize the observer
const swipeObserver = new SwipeObserver(document.querySelector('.swipe'))
...
//And later : update the options and activate the observer
swipeObserver.on({ threshold: 40})

off

/**
* @description : Deactivate the swipe listener
*/
off()
  
//Example
const swipeObserver = new SwipeObserver(document.querySelector('.swipe'), { active: true })
...
//And later
swipeObserver.off()

active

/**
* @getter : Return the state of the observer
*/
  
//Example
const swipeObserver = new SwipeObserver(document.querySelector('.swipe'))
console.log(swipeObserver.active)//Output false
...
//And later
swipeObserver.on()
console.log(swipeObserver.active)//Output true

threshold

/**
* @getter : Return the threshold value of the observer
* Threshold is the minimum distance in pixels 
* between the start and the end of the movement to detect a swipe
*/
  
//Example
const swipeObserver = new SwipeObserver(document.querySelector('.swipe'))
console.log(swipeObserver.threshold)//Output default value : 20
...
//And later : update the threshold value and activate the observer
swipeObserver.on({ threshold: 40 })
console.log(swipeObserver.threshold)//Output 40

Custom Event

The class uses the CustomEvent interface which is not understood by some older browsers like Internet Explorer.

So, if you want to support those oldies, you have to polyfill the CustomEvent interface.

Just add somewhere inside the <head> of your html :

<script>
  if (typeof window.CustomEvent !== "function") {
    var d = document, s = d.createElement('script');
    s.async = "false";
    s.defer = "true";
    s.src = "javascript_polyfills_folder_path/customEventPolyfill.js";
    d.head.appendChild(s);
  }
</script>

And create a customEventPolyfill.js with the folowing content :

(function(){
  if (typeof window.CustomEvent !== "function") {
    function CustomEvent ( event, params ) {
      params = params || { bubbles: false, cancelable: false, detail: null };
      var evt = document.createEvent( 'CustomEvent' );
      evt.initCustomEvent( event, params.bubbles, params.cancelable, params.detail );
      return evt;
    }
    window.CustomEvent = CustomEvent;
  }
})();

Prevent scroll

Basic truism : the swiping/swipe events are emitted while/after moving the pointer.
But, on touchscreen, such gestures are interpreted by the system as scroll requests.
This is not really a problem if the movement is horizontal and if the width of the body does not exceed that of the device because there is nothing to scroll.
But in the case of a vertical swipe, asking the browser to respond with a specific action rather than scrolling requires some diplomacy.
Nowadays, the easiest way to prevent gestures from being captured by the system is to use the touch-action css property.
It's now supported by all modern mobile browsers as you can see below :

By setting touch-action to none, the browser will ignore all touch events and therefore will not respond with a scroll when swiping vertically.
It's a little brutal but terribly effective.
You can make sure by playing with the first demo at the top of this page which uses precisely this method.

The major drawback is that if the user still wants to go down the page, he must scroll outside the observed element.
The operation is made particularly tricky or even impossible if the element in question takes up most of the space available on the screen.
The only way to handle this kind of situation is to resort to javascript and conditionally change the touch-action property value or call the preventDefault() method on the touchmove event to block scrolling.
This is what is done in the second demo below.

If you need to support older devices (as iOS before version 13), the body-scroll-lock package is a complete and well-know solution.
You might also be interested in a workaround exposed in this article.

Demo 2

Scroll down ENABLED
Scroll up ENABLED
Scroll down DISABLED

Try it...

Scroll up DISABLED

Here's the idea : if the user performs two successive vertical swipes in the same direction, the first is not detect by the browser as a scroll request but the second is.
To achieve this, after a vertical swipe detection, we simply change the value of the touch-action property from none to pan-up or pan-down depending on the direction of movement.

Sound too easy ?
You're right !

iOS does not understand pan-up and pan-down values.

The only workaround we have for it, is to call the preventDefault() method on the original touchmove event in the swiping listener of our observer.

//Store the last swipe direction ('left', 'right', 'up', 'down' or '')
let previousDir = ''
//Test the support of pan-up value for the css touch-action property
//First, we create a test container
const testFullTouchActionSupport = d.createElement('div')
//we set its touch-action property to pan-up
testFullTouchActionSupport.style.touchAction = 'pan-up'
//fullTouchActionSupport will be true if the pan-up value is understood by the browser
const fullTouchActionSupport = (testFullTouchActionSupport.style.touchAction === 'pan-up')
//In this case, we set the touch-action property to none until we detect a vertical swipe
//Scroll is blocked
if (fullTouchActionSupport) demo2Element.style.touchAction = 'none'
//Log the test result
console.log(`Css touch-action property ${fullTouchActionSupport ? 'supports' : 'does not support'} pan-up and pan-down values`)


//Swiping listener
demo2Element.addEventListener('swiping', (e) => {
  //If this is a vertical swipe 
  //and if its direction is different from the last swipe detected
  if (e.detail.pointerType != 'mouse' && e.detail.orientation == 'vert' && previousDir != e.detail.direction) {
    //e.detail.events.end is the original touchmove event
    const ev = e.detail.events.end
    //If the event is cancelable
    if (ev.cancelable) {
      //we prevent it
      ev.preventDefault()
      console.log('Scroll has been blocked.')
    } else {
      console.log("Scroll can't be blocked.")
    }
  }
  textDemo2.innerHTML = 'SWIPING ' + e.detail.direction.toUpperCase()
})

//Swipe listener
demo2Element.addEventListener('swipe', (e) => {
  if (e.detail.pointerType != 'mouse') {
    const dir = e.detail.direction
    //If it's a horizontal swipe
    if (e.detail.orientation == 'hor') {
      //Reset the container to its default state
      reset()
      //If it's a vertical swipe 
      //and if its direction is different from the last swipe detected
    } else if (previousDir != dir) {
      if (dir == 'down') {
        //Show the top banner
        demo2Element.classList.add('show_top')
        //Hide the bottom banner
        demo2Element.classList.remove('show_bottom')
      } else {
        //Hide the top banner
        demo2Element.classList.remove('show_top')
        //Show the bottom banner
        demo2Element.classList.add('show_bottom')
      }
      //If pan-up value is supported
      if (fullTouchActionSupport) {
        const touchActionDirection = (dir == 'down') ? 'up' : 'down'
        //Update the touch-action property
        demo2Element.style.touchAction = 'pan-' + touchActionDirection
      }
    }
    previousDir = dir
  }
  textDemo2.innerHTML = 'SWIPE ' + e.detail.direction.toUpperCase()
})

demo2Element.addEventListener('cancel', (e) => {
  textDemo2.innerHTML = 'CANCEL'
  previousDir = ''
  reset()
})

function reset() {
  //Reset the touc-action property to none
  if (fullTouchActionSupport) demo2Element.style.touchAction = 'none'
  //Hide the top banner
  demo2Element.classList.remove('show_top')
  //Hide the bottom banner
  demo2Element.classList.remove('show_bottom')
}