Simple Touch Events horizontal swiping for ReactJS image modal

I spent yesterday on an image modal gallery for this web app I’m working on. The web app uses ReactJS so it’s not so easy to just drop in any old plugin. We’re also not using jQuery, just React and vanilla JavaScript (ES2015 with Babel) for everything else.

For this image gallery, I implemented navigation with arrow keys, as well as forward/back buttons. But I wanted to add left and right swipe for touch devices. My manager suggested a library like Photoswipe, but that seemed like overkill for what we’re trying to accomplish (although there is a React version).

I couldn’t find a source that explained how to do simple swipe detection. There was this answer on Stackoverflow that seemed to head in the right direction, but it was terribly written. However, it was a starting point.

This only works with the Touch Events API, not Pointer Events, but that is something I’m going to work on in the future.

The following code uses some ES2015 features and syntax. If you’re not familiar, I recommend checking out Babel and also the great Code School course. This is a module named Swipe that gets called by my ImageModal component in React. I call it in componentDidMount with Swipe.init(DOMelement, callbackFunction); and then kill it in componentWillUnmount with Swipe.kill(DOMelement);. I only care about horizontal swiping in this instance, so there’s only code to account for that.

// event.which key codes for left and right arrow keys
// used by callback to chose which photo to go to
import { LEFT_ARROW, RIGHT_ARROW } from './constants';  
// min x delta swipe for horizontal swipe
const MIN_X = 30;  
// max y delta for horizontal swipe
const MAX_Y = 50;

let eventObj = {  
    startX: 0,
    startY: 0,
    endX: 0,
    endY: 0
};

let callback;

export function init(el, action) {  
  el.addEventListener('touchstart', handleStart, false);
  el.addEventListener('touchmove', handleMove, false);
  el.addEventListener('touchend', handleEnd, false);
  callback = action;
}

export function kill(el) {  
  el.removeEventListener('touchstart', handleStart);
  el.removeEventListener('touchmove', handleMove);
  el.removeEventListener('touchend', handleEnd);
  callback = undefined;
}

function handleStart(e) {  
  // assuming single touch, e.touches is an Array of all touches,
  // but with single touch there is only one element
  let touch = e.touches[0];
  eventObj.startX = touch.screenX;
  eventObj.startY = touch.screenY;
}

function handleMove(e) {  
  let touch = e.touches[0];
  eventObj.endX = touch.screenX;
  eventObj.endY = touch.screenY;
}

function handleEnd() {  
  let code;
  let xDelta = eventObj.startX - eventObj.endX;
  // check to see if the delta of X is great enough to trigger a swipe gesture
  // also see if the Y delta wasn’t too drastic to be considered horizontal
  if (Math.abs(xDelta) > MIN_X && Math.abs(eventObj.startY - eventObj.endY) < MAX_Y) {
    // acceptable swipe, now if it delta is negative, it’s a left swipe, otherwise right
    code = xDelta < 0 ? LEFT_ARROW : RIGHT_ARROW;
  }
  // trigger callback
  if (callback && code) {
    callback(code);
  }
}

There are improvements to be made. Since this is a modal, the rest of the app freezes up (intentionally) while open, so careful handling of touchstart and touchmove for scrolling performance isn’t needed here. There is a fun iOS Safari issue where using overflow: hidden on the body doesn’t actually prevent scrolling, so elsewhere I had to e.preventDefault(); on a touchmove event to prevent the background from scrolling.