I noticed last year that some of my projects behave inconsistently on touch devices. I didn't do anything about it. But then I was working on a tutorial about how I make my interactive tutorials, I had to show how to handle touch events, and I was embarrassed by my code. I decided to take a week to learn about mouse, touch, and pointer events.

That week turned into two. And three. And eight.

I learned a lot. Not only about the event handling spec, but also differences in browser behavior and operating system behavior.

State diagram for mouse and touch events

I was able to greatly simplify my code by using newer APIs and by removing unnecessary code.

The trouble with my code was that I was "cargo culting" my event handling code. I had lines like event.stopPropagation() without really thinking about or understanding why it was there. I went through and removed each line I wasn't sure about, and tested the behavior. I tested my code on Gecko/Firefox (Mac, Windows, Linux, Android), Blink/Chrome (Mac, Windows, Linux, Android), and WebKit/Safari (Mac, iPhone, iPad). I did not test on hoverable stylus, hybrid touch+mouse devices, or voice input. I tried dragging different types of objects with different types of drag behavior.

I made a test page where I printed out all the events, along with some of the additional fields like button and pointerId. I learned a lot this way. This page is also full of unorganized notes.

The biggest issues I wanted to solve were:

  1. Capture: with both mouse and touch, I want to handle drag events that go outside the diagram.
  2. Scrolling: on touch devices, dragging with the finger scrolls the page, but I also want dragging with the finger to move the object.

But in going through the tests I found lots of other situations I might want to handle: text selection, system drag, right click context menu, middle click autoscroll, holding down multiple buttons, and more. The right click context menu was especially interesting, as Windows, Mac, and Linux work quite differently! And Chrome, Safari, and Firefox also work quite differently.

I kept finding more and more weird edge cases! What happens when you start dragging on a phone and then rotate the phone to landscape mode? The behavior is different on iPhone and Android! What happens when you start dragging with a finger on iPad and then use the stylus? The stylus overrides the finger drag. What happens when you resize the window while you're dragging? What happens if you put the computer to sleep while dragging?

I just had to stop! I decided I should handle common cases but not worry about all the edge cases.

I made a test page where I listed some of the edge cases, and also tested different types of dragging. And then I wrote my recommended event handling code. And then I implemented this new code on some of my existing pages: mapgen4, hexagons, grids/edges, responsive design. It works really well on mapgen4. I had mixed results with hexagons, because that page sometimes uses different interactions for touch and mouse, so the unified event handlers weren't the right approach. The edges page never supported touch events, and now partially supports them. The responsive design page also never supported touch events, and now fully supports them.

Despite this project taking much longer than expected, I consider it to be a success. I'll continue updating these pages as I learn more. And I'd love to hear from you about what I got wrong. Start with the event handling page.

Labels:

0 comments: