UNPKG

five-bells-visualization

Version:
285 lines (254 loc) 9.28 kB
<!-- @license Copyright (c) 2014 The Polymer Project Authors. All rights reserved. This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt Code distributed by Google as part of the polymer project is also subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt --> <script> (function(scope) { 'use strict'; var async = scope.Base.async; var Gestures = { gestures: {}, // automate the event listeners for the native events // TODO(dfreedm): add a way to remove handlers. add: function(evType, node, handler) { // listen for events in order to "recognize" this event var g = this.gestures[evType]; var gn = '_' + evType; var info = {started: false, abortTrack: false, oneshot: false}; if (g && !node[gn]) { if (g.touchaction) { this._setupTouchAction(node, g.touchaction, info); } for (var i = 0, n, sn, fn; i < g.deps.length; i++) { n = g.deps[i]; fn = g[n].bind(g, info); sn = '_' + evType + '-' + n; // store the handler on the node for future removal node[sn] = fn; node.addEventListener(n, fn); } node[gn] = 0; } // listen for the gesture event node[gn]++; node.addEventListener(evType, handler); }, remove: function(evType, node, handler) { var g = this.gestures[evType]; var gn = '_' + evType; if (g && node[gn]) { for (var i = 0, n, sn, fn; i < g.deps.length; i++) { n = g.deps[i]; sn = '_' + evType + '-' + n; fn = node[sn]; if (fn){ node.removeEventListener(n, fn); // remove stored handler to allow GC node[sn] = undefined; } } node[gn] = node[gn] ? (node[gn] - 1) : 0; node.removeEventListener(evType, handler); } }, register: function(recog) { this.gestures[recog.name] = recog; }, // touch will make synthetic mouse events // preventDefault on touchend will cancel them, // but this breaks <input> focus and link clicks // Disabling "mouse" handlers for 500ms is enough _cancelFunction: null, cancelNextClick: function(timeout) { if (!this._cancelFunction) { timeout = timeout || 500; var self = this; var reset = function() { var cfn = self._cancelFunction; if (cfn) { clearTimeout(cfn.id); document.removeEventListener('click', cfn, true); self._cancelFunction = null; } }; var canceller = function(e) { e.tapPrevented = true; reset(); }; canceller.id = setTimeout(reset, timeout); this._cancelFunction = canceller; document.addEventListener('click', canceller, true); } }, // try to use the native touch-action, if it exists _hasNativeTA: typeof document.head.style.touchAction === 'string', // set scrolling direction on node to check later on first move // must call this before adding event listeners! setTouchAction: function(node, value) { if (this._hasNativeTA) { node.style.touchAction = value; } node.touchAction = value; }, _setupTouchAction: function(node, value, info) { // reuse custom value on node if set var ta = node.touchAction; value = ta || value; // set an anchor point to see how far first move is node.addEventListener('touchstart', function(e) { var t = e.changedTouches[0]; info.initialTouch = {x: t.clientX, y: t.clientY}; info.abortTrack = false; info.oneshot = false; }); node.addEventListener('touchmove', function(e) { // only run this once if (info.oneshot) { return; } info.oneshot = true; // "none" means always track if (value === 'none') { return; } // "auto" is default, always scroll // bail-out if touch-action did its job // the touchevent is non-cancelable if the page/area is scrolling if (value === 'auto' || !value || (ta && !e.cancelable)) { info.abortTrack = true; return; } // check first move direction // unfortunately, we can only make the decision in the first move, // so we have to use whatever values are available. // Typically, this can be a really small amount, :( var t = e.changedTouches[0]; var x = t.clientX, y = t.clientY; var dx = Math.abs(info.initialTouch.x - x); var dy = Math.abs(info.initialTouch.y - y); // scroll in x axis, abort track if we move more in x direction if (value === 'pan-x') { info.abortTrack = dx >= dy; // scroll in y axis, abort track if we move more in y direction } else if (value === 'pan-y') { info.abortTrack = dy >= dx; } }); }, fire: function(target, type, detail, bubbles, cancelable) { return target.dispatchEvent( new CustomEvent(type, { detail: detail, bubbles: bubbles, cancelable: cancelable }) ); } }; Gestures.register({ name: 'track', touchaction: 'none', deps: ['mousedown', 'touchmove', 'touchend'], mousedown: function(info, e) { var t = e.currentTarget; var self = this; var movefn = function movefn(e, up) { if (!info.tracking && !up) { // set up tap prevention Gestures.cancelNextClick(); } // first move is 'start', subsequent moves are 'move', mouseup is 'end' var state = up ? 'end' : (!info.started ? 'start' : 'move'); info.started = true; self.fire(t, e, state); e.preventDefault(); }; var upfn = function upfn(e) { // call mousemove function with 'end' state movefn(e, true); info.started = false; // remove the temporary listeners document.removeEventListener('mousemove', movefn); document.removeEventListener('mouseup', upfn); }; // add temporary document listeners as mouse retargets document.addEventListener('mousemove', movefn); document.addEventListener('mouseup', upfn); }, touchmove: function(info, e) { var t = e.currentTarget; var ct = e.changedTouches[0]; // if track was aborted, stop tracking if (info.abortTrack) { return; } e.preventDefault(); // the first track event is sent after some hysteresis with touchmove. // Use `started` state variable to differentiate the "first" move from // the rest to make track.state == 'start' // first move is 'start', subsequent moves are 'move' var state = !info.started ? 'start' : 'move'; info.started = true; this.fire(t, ct, state); }, touchend: function(info, e) { var t = e.currentTarget; var ct = e.changedTouches[0]; // only trackend if track was started and not aborted if (info.started && !info.abortTrack) { // reset started state on up info.started = false; var ne = this.fire(t, ct, 'end'); // iff tracking, always prevent tap e.tapPrevented = true; } }, fire: function(target, touch, state) { return Gestures.fire(target, 'track', { state: state, x: touch.clientX, y: touch.clientY }); } }); // dispatch a *bubbling* "tap" only at the node that is the target of the // generating event. // dispatch *synchronously* so that we can implement prevention of native // actions like links being followed. // // TODO(dfreedm): a tap should not occur when there's too much movement. // Right now, a tap can occur when a touchend happens very far from the // generating touch. // This *should* obviate the need for tapPrevented via track. Gestures.register({ name: 'tap', deps: ['click', 'touchend'], click: function(info, e) { this.forward(e); }, touchend: function(info, e) { Gestures.cancelNextClick(); this.forward(e); }, forward: function(e) { // prevent taps from being generated from events that have been // canceled (e.g. via cancelNextClick) or already handled via // a listener lower in the tree. if (!e.tapPrevented) { e.tapPrevented = true; this.fire(e.target); } }, // fire a bubbling event from the generating target. fire: function(target) { Gestures.fire(target, 'tap', {}, true); } }); scope.Gestures = Gestures; })(Polymer); </script>