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.

The mobile experience in Door County, Wisconsin

I just got back from a short trip to Door County, Wisconsin. If you’re not familiar, it’s a popular weekend getaway spot for people all over Wisconsin (and Chicago too!). It’s scenic—mostly rural and rustic—and filled with great restaurants and wineries. And it plays host to throngs of Jamie Lee Curtis lookalikes that frequent the many shops: antique, crafts, clothing, etc. If you imagine Wisconsin as a hand, it’s the peninsular thumb.

I like to go there often with my (as of a few days ago) wife. We like to hike and eat—just get away, alone. I usually bring only my phone. Occasionally I’ll take the iPad, rarely will I bring a laptop.

This makes the mobile experience crucial. The peninsula doesn’t get great cell reception. Usually it’s a bar or two and 3G. I’m of the generation that looks up businesses online and gets annoyed when a business does not have a web presence (Facebook doesn’t count). If I sound like a damned young person, sorry (not sorry), but this is the future. There are so many restaurants in Door County, I need look up menus and hours online to help narrow down where to eat.

This causes problems.

One of my favorite restaurants in Door County is The Cookery. We went there for breakfast today. Before we left, I wanted to check their hours and also peek at the menu. I went to their site, immediately saw their logo, then waited for about a minute for the rest of the page to load.

Looking at the mobile view in Chrome DevTools right now, the page weight sits at 8.7MB and 96 requests. It is responsive, but it is not mobile friendly. In an area that gets shoddy reception, downloading that much data not only digs into my data plan but also kills my battery as the phone is constantly searching for reception.

Thankfully, the hours are on the home page, but they’re all the way at the bottom of a 7,500-pixel-high page (on an iPhone 5). I have to scroll through 7,400 pixels of marketing filler to get any useful information. I don’t need to know the history of the restaurant and the ethos behind it. If I do, I’ll look for that section of the site. Give me the most important information first—especially on mobile.

Knowing the hours, I wanted to find the menu. There are no links in the footer, so I scroll back to the top. I don’t see anything. I do see a weird clock emoji on the hero image. I noticed similar clock emojis on a different Door County restaurant site the previous day, in place of arrows for a carousel.

Then it dawned on me: content blockers on iOS9 with external fonts disabled kill CDN-hosted icon fonts. Instead of a useless hamburger icon, I saw a flower emoji, tapped it, and the site menu opened. 5.9MB later and the breakfast menu loaded.

At least the site tried to work on mobile. Most Door County restaurants have desktop-only views (which oddly tend to have a smaller page weight because they don’t clog the page with large high-res photos).

This is why I care about mobile-first and performance-driven design. The situation in Door County just exacerbates the problems with ignoring mobile and performance. There’s poor reception, so a webpage is already going to load slowly, if at all. And I might only be able to download a little bit of data before I lose my connection. Even if the page loads, it might be desktop only, with difficult-to-navigate drop down menus. Or icon fonts are used, but external fonts are blocked, so esoteric emojis show up instead of the hamburger icon. Or the most important information—the reason I’m on your site while on a phone just miles away from your business—is hidden at the bottom of the page or difficult to find elsewhere on the site.

Design for context. Design for performance. Design for the user. Because it’s fucking frustrating when a site gets in the way of the people trying to use it.

100 Words 100

This is the end.

I could do something similar to Jeremy Keith, but nah.

I’m surprised I finished (and I only skipped a handful of days). Instead of taking one hundred days, this took one hundred six (maybe, didn’t count). In the past, I’ve made plenty of New Year’s resolutions to write more. I didn’t this year. Yet now I’ve written more than I ever have (outside of my school years).

I don’t want to stop. When I was younger, I really enjoyed writing and wanted to write more as I grew up.

I’m getting there.

100 Words 099

Yes I saw super moon tonight (I’m not a monster).

I’ve been fascinated with astronomy and space ever since I took an astronomy course my sophomore year of college. But I feel weird when everyone is posting about a particular news story or event on social media. Perhaps it’s my natural contrarianism that immediately wants to mock the big story everyone is talking about.

This lunar eclipse is a cool rare event—can’t we enjoy it and not let the world know? Do I need to be the nth person on my feed posting a blurry photo from my iPhone?

100 Words 098

Tonight I alphabetized one hundred eighty-three place cards. I found myself mentally singing the alphabet song way more often than I’m comfortable admitting. After the first handful of cards, realizing that J came before K was easier.

But those middle letters always trip me up. I get the beginning six letters. Those are obvious. And O through T are a cinch. But the last few can stump me (except X, Y, and Z, naturally). Yet I can’t start the song midway through—that’d just be crazy. It takes time to sing to U to remember it comes before W.