├── LICENSE ├── README.md ├── drags.html ├── drags.js ├── index.html ├── taps.html └── taps.js /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Skookum Digital Works 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Input Examples on the Web 2 | 3 | Examples of handling mouse, touch and pointer events on the web. 4 | 5 | These examples are intended for educational purposes; it is highly recommended 6 | against using any of these in a production environment. Use an alternative, 7 | such as: 8 | 9 | * [PointerGestures](https://github.com/Polymer/PointerGestures), 10 | * [HandJS](https://handjs.codeplex.com/) 11 | * [Hammer.js](http://hammerjs.github.io/) 12 | 13 | ## License 14 | 15 | The MIT License (MIT) 16 | 17 | Copyright (c) 2014 Skookum Digital Work 18 | 19 | Permission is hereby granted, free of charge, to any person obtaining a copy 20 | of this software and associated documentation files (the "Software"), to deal 21 | in the Software without restriction, including without limitation the rights 22 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 23 | copies of the Software, and to permit persons to whom the Software is 24 | furnished to do so, subject to the following conditions: 25 | 26 | The above copyright notice and this permission notice shall be included in 27 | all copies or substantial portions of the Software. 28 | 29 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 30 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 31 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 32 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 33 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 34 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 35 | THE SOFTWARE. 36 | 37 | -------------------------------------------------------------------------------- /drags.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tappable 6 | 7 | 8 | 31 | 32 | 33 | 37 |
38 |
Drag me about!
39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /drags.js: -------------------------------------------------------------------------------- 1 | var hasPointer = !!(window.PointerEvent || window.navigator.msPointerEnabled); 2 | var hasTouch = !!window.TouchEvent || true; 3 | 4 | var POINTER_DOWN = 'MSPointerDown'; 5 | var POINTER_UP = 'MSPointerUp'; 6 | var POINTER_MOVE = 'MSPointerMove'; 7 | 8 | if (window.PointerEvent) { 9 | POINTER_DOWN = 'pointerdown'; 10 | POINTER_UP = 'pointerup'; 11 | POINTER_MOVE = 'pointermove'; 12 | } 13 | var log = document.querySelector('#log'); 14 | 15 | var draggable = document.querySelector('#draggable'); 16 | 17 | var events = {}; 18 | 19 | /** 20 | * @param {Event} event 21 | * @param {Element} target 22 | * @return {String} 23 | */ 24 | function id(event, target) { 25 | return event.type.slice(0, 5) + target.id; 26 | } 27 | 28 | if (hasPointer) { 29 | draggable.addEventListener(POINTER_DOWN, dragStart, false); 30 | } 31 | else { 32 | draggable.addEventListener('mousedown', dragStart, false); 33 | 34 | if (hasTouch) { 35 | draggable.addEventListener('touchstart', dragStart, false); 36 | } 37 | } 38 | 39 | function set(id, prop, value) { 40 | if (typeof prop === 'object' && typeof value === 'undefined') { 41 | events[id] = value; 42 | return; 43 | } 44 | if (typeof id !== 'string') id = idFor(id); 45 | if (typeof events[id] === 'undefined') { 46 | events[id] = {}; 47 | } 48 | // ignore compatibility events 49 | if (typeof events[id][prop] === 'undefined') { 50 | events[id][prop] = value; 51 | } 52 | } 53 | 54 | function dragStart(event) { 55 | // ignore non-left clicks 56 | if (event.type === 'mousedown' && event.which !== 1) return; 57 | 58 | var target = event.currentTarget; 59 | var type = /^(pointer|mouse|touch)/i.exec(event.type)[0]; 60 | var moveEvent = type + 'move'; 61 | var endEvent = type + 'up'; 62 | var o = extract(event); 63 | var pos = extractPositioning(target); 64 | 65 | // assume uniqueness in key 66 | switch (event.type.toLowerCase()) { 67 | case 'mspointerdown': 68 | case 'pointerdown': 69 | event.currentTarget.setPointerCapture(event.pointerId); 70 | /* Direct all pointer events to JavaScript code. */ 71 | event.currentTarget.style.msTouchAction = 'none'; 72 | event.currentTarget.style.touchAction = 'none'; 73 | break; 74 | case 'touchstart': 75 | o.x = event.changedTouches[0].clientX; 76 | o.y = event.changedTouches[0].clientY; 77 | endEvent = type + 'end'; 78 | break; 79 | case 'mousedown': 80 | target = document.body; 81 | break; 82 | } 83 | if (event.type.toLowerCase() !== 'mousedown') { 84 | // prevent the cascade 85 | event.preventDefault(); 86 | } 87 | 88 | var __id = id(event, target); 89 | set(__id, 'x', o.x - pos.x); 90 | set(__id, 'y', o.y - pos.y); 91 | set(__id, 'originalTarget', o.originalTarget); 92 | set(__id, 'timeStamp', o.timeStamp); 93 | 94 | target.addEventListener(moveEvent, dragMove, false); 95 | target.addEventListener(endEvent, dragEnd, false); 96 | } 97 | 98 | function dragMove(event) { 99 | var target = event.currentTarget; 100 | var o = extract(event); 101 | event.preventDefault(); 102 | 103 | if (/^mouse/.test(event.type)) 104 | target = document.body; 105 | 106 | var __id = id(event, target); 107 | var oo = events[__id]; 108 | if (typeof oo === 'undefined') return; 109 | 110 | var xDiff = o.x - oo.x; 111 | var yDiff = o.y - oo.y; 112 | 113 | requestAnimationFrame(function() { 114 | position(oo.originalTarget, xDiff, yDiff); 115 | }); 116 | } 117 | 118 | function dragEnd(event) { 119 | var target = event.currentTarget; 120 | var o = extract(event); 121 | var type = /^(pointer|mouse|touch)/.exec(event.type)[0]; 122 | var moveEvent = type + 'move'; 123 | var endEvent = type + 'up'; 124 | 125 | if (event.type === POINTER_DOWN) 126 | event.currentTarget.releasePointerCapture(event.pointerId); 127 | else if (event.type === 'mousedown') 128 | target = document.body; 129 | 130 | var __id = id(event, target); 131 | var oo = events[__id]; 132 | target.removeEventListener(moveEvent, dragEnd); 133 | target.removeEventListener(event.type, dragEnd); 134 | 135 | var xDiff = o.x - oo.x; 136 | var yDiff = o.y - oo.y; 137 | 138 | requestAnimationFrame(function() { 139 | position(oo.originalTarget, xDiff, yDiff); 140 | delete events[__id]; 141 | }); 142 | } 143 | 144 | function extract(event) { 145 | var x = event.clientX, y = event.clientY; 146 | if (event.type.match(/touch/)) { 147 | x = event.changedTouches[0].clientX; 148 | y = event.changedTouches[0].clientY; 149 | } 150 | 151 | return { 152 | originalTarget: event.currentTarget, 153 | timeStamp: event.timeStamp || Date.now(), 154 | x: x, 155 | y: y, 156 | }; 157 | } 158 | 159 | function position(node, x, y) { 160 | node.style.transform = 'translate(' + x + 'px ,' + y + 'px)'; 161 | node.style.webkitTransform = 'translate(' + x + 'px ,' + y + 'px)'; 162 | } 163 | 164 | function extractPositioning(node) { 165 | var transform = node.style.transform || node.style.webkitTransform; 166 | var x = 0, y = 0; 167 | if (typeof transform !== 'undefined') { 168 | var parts = transform.match(/(-?\d+)/g); 169 | x = parts ? Number(parts[0]) : 0; 170 | y = parts ? Number(parts[1]) : 0; 171 | } 172 | return {x: x, y: y}; 173 | } 174 | 175 | 176 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tappable 6 | 7 | 8 | 23 | 24 | 25 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /taps.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Tappable 6 | 7 | 8 | 42 | 43 | 44 | 48 |
Tap. 49 |

Use JS to manage `tap` intent and fire as soon as possible.

50 |
51 |
Click. 52 |

Bind to touch and pointer start events to track time from intent start to browser action.

53 |
54 | 55 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /taps.js: -------------------------------------------------------------------------------- 1 | var __slice = function(a, b) { return Array.prototype.slice.call(a, b); }; 2 | var hasPointer = !!(window.PointerEvent || window.navigator.msPointerEnabled); 3 | var hasTouch = !!window.TouchEvent; 4 | 5 | var ENTER_KEY = 13; 6 | 7 | var POINTER_DOWN = 'MSPointerDown'; 8 | var POINTER_UP = 'MSPointerUp'; 9 | var POINTER_MOVE = 'MSPointerMove'; 10 | 11 | if (window.PointerEvent) { 12 | POINTER_DOWN = 'pointerdown'; 13 | POINTER_UP = 'pointerup'; 14 | POINTER_MOVE = 'pointermove'; 15 | } 16 | 17 | var tappable = document.querySelector('#tappable'); 18 | var clickable = document.querySelector('#clickable'); 19 | var output = document.querySelector('#output'); 20 | 21 | var events = {}; 22 | 23 | if (hasPointer) { 24 | tappable.addEventListener(POINTER_DOWN, tapStart, false); 25 | clickable.addEventListener(POINTER_DOWN, clickStart, false); 26 | } 27 | else { 28 | tappable.addEventListener('mousedown', tapStart, false); 29 | clickable.addEventListener('mousedown', clickStart, false); 30 | 31 | if (hasTouch) { 32 | tappable.addEventListener('touchstart', tapStart, false); 33 | clickable.addEventListener('touchstart', clickStart, false); 34 | } 35 | } 36 | 37 | clickable.addEventListener('click', clickEnd, false); 38 | 39 | function id(eventType, target) { 40 | return eventType.slice(0, 5) + target.id; 41 | } 42 | 43 | function idFor(eventOrType, target) { 44 | if (typeof eventOrType === 'string') { 45 | return id(eventOrType, target); 46 | } 47 | return id(eventOrType.type, eventOrType.target); 48 | } 49 | 50 | function set(id, prop, value) { 51 | if (typeof id !== 'string') id = idFor(id); 52 | if (typeof events[id] === 'undefined') { 53 | events[id] = {}; 54 | } 55 | // ignore compatibility events 56 | if (typeof events[id][prop] === 'undefined') { 57 | events[id][prop] = value; 58 | } 59 | } 60 | 61 | function get(id, prop) { 62 | if (typeof id !== 'string') id = idFor(id); 63 | return events[id][prop] || null; 64 | } 65 | 66 | function clickStart(event) { 67 | set(id('click', clickable), 'clickStart', Date.now()); 68 | } 69 | 70 | function diff(start, end) { 71 | return Math.abs(start - end); 72 | } 73 | 74 | function tapStart(event) { 75 | bindEventsFor(event.type, event.target); 76 | if (typeof event.setPointerCapture === 'function') { 77 | event.currentTarget.setPointerCapture(event.pointerId); 78 | } 79 | // prevent the cascade 80 | event.preventDefault(); 81 | set(event, 'tapStart', Date.now()); 82 | } 83 | 84 | function tapEnd(target, event) { 85 | unbindEventsFor(event.type, target); 86 | var _id = idFor(event); 87 | log('Tap', diff(get(_id, 'tapStart'), Date.now())); 88 | setTimeout(function() { 89 | delete events[_id]; 90 | }); 91 | } 92 | 93 | function clickEnd(event) { 94 | var _id = id('click', clickable); 95 | log('Click', diff(get(_id, 'clickStart'), Date.now())); 96 | setTimeout(function() { 97 | delete events[_id]; 98 | }); 99 | } 100 | 101 | function bindEventsFor(type, target) { 102 | var end = curry(tapEnd, target); 103 | set(idFor(type, target), 'callback', end); 104 | 105 | if (type === 'mousedown') { 106 | document.addEventListener('mouseup', end, false); 107 | } 108 | else if (type === 'touchstart') { 109 | target.addEventListener('touchend', end, false); 110 | } 111 | else { 112 | target.addEventListener(POINTER_UP, end, false); 113 | } 114 | } 115 | 116 | function unbindEventsFor(type, target) { 117 | var id = idFor(type, target); 118 | var endEvent = events[id].callback; 119 | document.removeEventListener('mouseup', endEvent); 120 | target.removeEventListener('touchend', endEvent); 121 | target.removeEventListener(POINTER_UP, endEvent); 122 | } 123 | 124 | function curry(fn /*, ...args*/) { 125 | var args = __slice(arguments, 1); 126 | return function() { 127 | fn.apply(this, args.concat(__slice(arguments, 0))); 128 | }; 129 | } 130 | 131 | function log(type, length) { 132 | var li = document.createElement('li'); 133 | li.innerHTML = '' + type + ' ' + length + 'ms'; 134 | output.insertBefore(li, output.firstChild); 135 | } 136 | 137 | function clear() { 138 | output.innerHTML = ''; 139 | } 140 | 141 | --------------------------------------------------------------------------------