My JavaScript book is out! Don't miss the opportunity to upgrade your beginner or average dev skills.

Saturday, August 16, 2014

PointerEvents No More

It is stated and reasoned in this bug related comment that PointerEvents will not land in Blink, the Chrome/ium DOM related engine.
The brief summary, from the technical point of view, is the following:
  1. Touch events already demonstrated to work on the Mobile Web, no need to have more complex and, for mobile Web purpose, redundant API
  2. The hit-test model proposed by PointerEvents will penalize performance. Being such model not even available in Android or iOS it makes no sense to further penalize the Web against native.
  3. More complexity but less power, since it's not possible to intercept touch move while scrolling an element, being PointerEvents and scrolling mutually exclusive.
There are other reasons in the related thread and one of most important sentence, in my opinion, is summarized here:
The main shift in thinking for us has been from a position of a desktop-dominated web evolving slowly to enable mobile scenarios, to the realization that phones/tablets will soon dominate Internet usage and that the web is loosing to native mobile platforms.

Where PointerEvents failed

This is the most important argument against PointerEvents: touch capable Mobile Web has been around for 6+ years now and that Hardware still works properly with modern websites. Having 2 ways to end up dealing in the same exact way touch actions is redundant/duplicated code nobody wants to implement for a Mobile interface.

more complexity to do the same thing

If you have some experience with PoitnerEvents you know you ended up writing code like this:
// Handler => https://gist.github.com/WebReflection/9814013
// just a basic utility to simplify events managment
Handler.add(node, {
  // simulating touchstart and mousedown
  pointerdown: function (e) {
    if (e.pointerType == 'touch' ||
        e.pointerType == e.MSPOINTER_TYPE_TOUCH) {
      e.currentTarget.setPointerCapture(e.pointerId);
      e.preventDefault();
      // now prepare to move
    } else if(e.pointerType == 'mouse') {
      // mousedown, maybe start dragging?
    }
  },
  // simulating touchmove and mousemove
  pointermove: function (e) {
    if (e.pointerType == 'touch' ||
        e.pointerType == e.MSPOINTER_TYPE_TOUCH) {
      e.preventDefault();
      // do the touchmove like thing
    } else if(e.pointerType == 'mouse') {
      // mousemove, drag
    }
  },
  // simulating touchend
  pointerup: function (e) {
    if (e.pointerType == 'touch' ||
        e.pointerType == e.MSPOINTER_TYPE_TOUCH) {
      e.currentTarget.releasePointerCapture(e.pointerId);
      e.preventDefault();
      // clear state
    } else if(e.pointerType == 'mouse') {
      // mouseup, stop drag
    }
  },

  // for backward compatibility
  _fixed: function(e, method, pointerType) {
    e.setPointerCapture = e.msSetPointerCapture;
    return e;
  },
  MSPointerDown: function (e) {
    this.pointerdown(this._fixed(e));
  },
  MSPointerMove: function (e) {
    this.pointermove(this._fixed(e));
  },
  MSPointerUp: function (e) {
    this.pointerup(this._fixed(e));
  }
});
Not only we have coupled different meaning into a single stream of events, inevitably messing up readability and merging concerns because of different behaviors, but what we basically did on touch side is reflecting exactly this logic:
Handler.add(node, {
  touchstart: function (e) {
    // to also avoid dispatched mouse listeners
    e.preventDefault();
    // prepare to move
  },
  touchmove: function (e) {
    e.preventDefault();
    // do the touchmove thing
  },
  touchend: function (e) {
    e.preventDefault();
    // clear state
  }
});
If we want to also implement mouse events, how about doing in the universally compatible way we all know since 90s ?
Handler.add(node, {
  mousedown: function (e) {
    // prepare for dragging
  },
  mousemove: function (e) {
    // drag
  },
  mouseup: function (e) {
    // drop
  }
});
How cleaner is the code we all understand, write, and read compared with the PointerEvents counterpart?

no reliable polyfills

Despite the amount of attempts out there, setPointerCapture is implicitly invoked by default in every Touch based device. This basically means that unless that polyfill takes over the entire DOM interaction, killing performance already problematic even if implemented natively also due that hit-test target mentioned at the beginning, there is no way to have the exact same, default, behavior IE10 or IE11 implement.
This is not true the other way round, we can use PointerEvents to simulate Touch as shown before acting by default like them.
// demo only; don't use this code in prod!
Handler.add(node, {
  touchstart: function (e) {
    // prepare for move
  },
  touchmove: function (e) {
    // move
  },
  touchend: function (e) {
    // that's it
  },

  // use IE events to simulate touch
  pointerdown: function (e) {
    if (e.pointerType == 'touch') {
      e.currentTarget.setPointerCapture(e.pointerId);
      e.touchlist = [e];
      this.touchstart(e);
    }
  }

});
Luckily we don't have to take care about this mess, we should just use touch events that finally landed in IE11 Update 1 and eventually, if strictly necessary, we can put upfront a polyfill like ie-touch until all Windows Phone will get the update 1 installed (AFAIK this should happen around the end of August or beginning of September).

Tablet or Desktop ?

If we want to support special or different behaviors between touch and mouse we can simply split the logic and better organize it as shown before. Basically, instead of doing this per each bloody event:
node.addEventListener('pointerdown', function (e) {
  switch(e.pointerType) {
    case 'touch':
    case 'pen':
    case e.MSPOINTER_TYPE_TOUCH:
    case e.MSPOINTER_TYPE_PEN:
      // do touch things
      break;
    case 'mouse':
    case e.MSPOINTER_TYPE_MOUSE:
      // do mouse things
      break;
  }
});
We do this + that:
node.addEventListener('touchstart', function (e) {
  // finger or pen
});
node.addEventListener('mousedown', function (e) {
  // mouse indeed
});
This will also avoid O(n) checks multiplied by every input device event that will trigger on the browser.

So no "pen" anymore ?

I think pen is rather OK as touch device and it's in this event that I'd implement eventually pressure and angle for a simplified graceful enhancement over incompatible old systems that still will work as regular touch events.

Very different worlds

About Table VS Desktop there is another basic concept to consider: nobody drags with touch events, everyone drags with mouse. This is a fundamental split logic we should not underestimate. With touch screens we swipe, spread, pinch, tap, double tap, longpress to action while we right click instead or just point and click, or double click, with possible drag. Accordingly, The only event that has similar intent is click, irony wants that such event already works as expected with every device ... put in click what should have a click behavior, do not merge together any other gesture or intent into a single stream if you don't want to end up with spaghetti listeners hell.

In a nutshell

The initial idea behind PointerEvents was great but it was already showing how confined in an old mouse based world it was. Having all sort of events that might or might not be fired, just consider pointerover that won't happen the way we imagine if we touch the screen, was making the API itself inconsistent with developer expectations.
Moving the problem from a proper listener name, touch or mouse, into a pointerType event property hasn't solved a thing, hasn't worked as expected, hasn't improved performance, hasn't made the code easier to maintain or write ... it messed up listeners instead, adding extra complexity or unexpected default behaviors (i.e. the NON capture for touch events unshimmable as it is) where all we'll end up doing, as developers, is to simplify the logic down to what is possible already with Touch events.

This is Great!

I would like to underline that having specs like that and having played around even in production with those specs has been a great way to evaluate how nicely implemented, or how much troublesome, would these specs have been.
Seeing Microsoft recognizing it was important to support what devs wrote already and what mobile web is these days, bringing Touch events into IE11 Update 1, has been a very welcome and nice move we should all thank them about.
This is the web I like, where instead of silly pointless and endless discussions, we can go down to the concrete and not at all costs stubborn de-facto solution that everyone already used and is happy with and eventually change, or add, something documented and easy to use instead of playing "who's got the lauder voice".
Thanks to all people that helped with PointerEvents and their failure, now please let's make Touch and TouchList as cross platform as possible, including all micro gotchas with preventDefault and scrolling behaviors.

3 comments:

Jacob Rossi said...

more complexity
Actually, even by Google's own account, pointer events usually results in less complexity. Here's an example from Google's presentation at the browser input meeting at Microsoft recently.

http://docs.google.com/presentation/d/1qRqLKQjOnGgrM-UkMAb2RH6mQCFQHk8R01s5qvjm2Po/edit#slide=id.g355c5631f_19

And there's several corroborating statements from web devs over on Google's issue for this feature.

http://code.google.com/p/chromium/issues/detail?id=162757#c33
http://code.google.com/p/chromium/issues/detail?id=162757#c63

I think the example in this post looks complicated because it also tries to be compatible with an outdated version of the spec (IE10's prefixed implementation), which isn't necessary when discussing the future of the web. Additionally, your touch event example doesn't put in the code to iterate through the touch list, which adds more complexity that pointer events does not have.

reliable polyfills
The polymer polyfill is nearly 100% faithful, especially now that Chrome supports touch-action. Hit-testing in browsers that only support Touch Events is achieved using one API call: elementFromPoint(). There should not be a need to "take over the entire DOM" and indeed Polymer 's polyfill does not do this to my knowledge.

Moreover, whether a reliable polyfill can be built shouldn't be the issue in deciding what browsers implement natively. It is important for experimentation. Even with Google walking away from PE, they've publically committed to fixing any issues that prohibit quality polyfills.

http://lists.w3.org/Archives/Public/public-pointer-events/2014JulSep/0080.html

tablet or desktop
O(n) sounds bad, but n for devices today is <=3 and thus negligible.

Again, this code is overly complex because it's trying to handle an outdated standard too. This is the code per spec:

node.addEventListener("pointerdown", function (e) {
if(e.pointerType == "touch") {

}else if (e.pointerType == "mouse") {

}});

The this + that model would be great if sites were encouraged to do that. Unfortunately, they don't. We experimented with Touch Events on mouse enabled devices (desktop) and found 10% of the top sites on the web failed to wire up both touch and mouse handlers. This means, supporting Touch Events on a mouse enabled device often breaks the mouse. :-(

if(window.TouchEvent) {
//wire up touch events
}else{
// wire up mouse events
}

This is great
Unfortunately, there's still a lot of issues with touch events, and Google agrees. The proposals to try to fix these issues in TE haven't been met with positivity from other browsers.

http://lists.w3.org/Archives/Public/public-pointer-events/2014JulSep/0080.html

The recent responses on Google's issue tracker, Twitter, privately to Microsoft, amongst frameworks like jQuery/Dojo (http://lists.w3.org/Archives/Public/public-pointer-events/2014JulSep/0072.html), etc. tells us that this decision isn't what developers want.

Microsoft wants developers to use what input model works best for them. Based on the feedback we've been given from the community, we think that's Touch Events for best here & now interop and Pointer Events as a better way forward. Firefox and Opera also seem to agree.

For me personally, given all of that, I think this is a premature decision. I invite web developers to continue to weigh in to Microsoft (@iedevchat on Twitter) and Google.

This shouldn't be a decision made by browser makers but rather by web developer feedback.

Andrea Giammarchi said...

* more complexity
despite IE10 is still a thing (I have a WP8 phone), you need to think the event might be or not a mouse event. You need to put more complexity inside a listener that will inevitably have dual behavior or ignore eventually touch or mouse, and what about pen? None of your example will work with pen ... is pen different? It does not matter, Touch is about touch, is also semantic, and it's related to touch ... you don't have to split your logic inside a listener, you set your logic through expected event type.

* reliable polyfills
"nearly 100% faithful" is a no-go for production and backward compatible browsers a la Android 2.X or others that will inevitably slow down everything. PointerEvent Polyfills are a no go also for performance and I'd never use them on top of touch events.

Do I want to write twice the code that will handle pointer events same way touch does already on mobile or mouse does already on desktop? I don't think so.

* tablet or desktop
means mixing different behavior in a single stream splitting the stream in two unrelated parts.
This does not make usage of {handleEvent:callback} handlers better, does not make listeners fasters, does not make them simpler, it gets on the way per each interaction.
What is this solving? IMO nothing at all that couldn't be solved better and semantically keeping the logic in event names, instead of inside listeners.

That if/else is redundant, unnecessary, pointless when you can define behaviors you want through touch or mouse instead of mixing up two different way to interact.

I wish you read a bit more carefully my points here ...


* This is great
there are problems with Touch events same as there are problems with PointerEvents and if "Microsoft wants developers to use what input model works best for them" then Microsoft did a good choice to put Touch Events in their latest browser because developers already chose them and never even bothered with pointer events.

Asking developers to use a polyfill that will slow down older mobile phone is a nogo, asking them to write twice everything is a non-sense ... I don't see any advantage on using PointerEvents, these only gave me headaches with listeners and handlers and nothing more than that, I'd like to see where is the magic or revelation 'cause I've used them and never liked a thing about them.

TouchList is also mentioned in one of my snippets, the fact you don't have a touchlist in PointerEvents means you have no idea how many fingers are on the screen if you don't manually track by id ... tell me how less complex is to deal with PointerEvents here too then or what exactly PointerEvents solved in your case.

Also, I've worked on Mobile Twitter site for a while and never heard anyone complaining about Touch Events except few quirks because of some crappy browser so I am not sure what these dojo/jquery develoeprs expect exactly from PointerEvents but as a dev, I've used them for real and I don't need them.

Andrea Giammarchi said...

P.S. by taking over the entire DOM I meant forcing every listener to use elementFromPoint or force surrounding libraries to interact in a different way they usually do or wrap listeners to implicitly do these operations ... for which goal, exactly?

What are PointerEvents bringing in your plate that Touch events cannot ? Developers don't use both mouse and touch? Maybe they don't want/need to or they don't care for their target ... also because they most likely never targeted IE10+