3 // Adapted from phantom limb by brian cartensen
5 function FingerBlast(element) {
6 this.element = typeof element == 'string' ? document.querySelector(element) : element;
10 FingerBlast.prototype = {
21 var activate = this.activate.bind(this);
22 var deactivate = this.deactivate.bind(this);
24 function contains (element, ancestor) {
25 var descendants, index, descendant;
26 if ("compareDocumentPosition" in ancestor) {
27 return !!(ancestor.compareDocumentPosition(element) & 16);
28 } else if ("contains" in ancestor) {
29 return ancestor != element && ancestor.contains(element);
31 for (descendants = ancestor.getElementsByTagName("*"), index = 0; descendant = descendants[index++];) {
32 if (descendant == element) return true;
38 this.element.addEventListener('mouseover', function (e) {
39 var target = e.relatedTarget;
40 if (target != this && !contains(target, this)) activate();
43 this.element.addEventListener("mouseout", function (e) {
44 var target = e.relatedTarget;
45 if (target != this && !contains(target, this)) deactivate(e);
49 activate: function () {
50 if (this.active) return;
51 this.element.addEventListener('mousedown', (this.touchStart = this.touchStart.bind(this)), true);
52 this.element.addEventListener('mousemove', (this.touchMove = this.touchMove.bind(this)), true);
53 this.element.addEventListener('mouseup', (this.touchEnd = this.touchEnd.bind(this)), true);
54 this.element.addEventListener('click', (this.click = this.click.bind(this)), true);
58 deactivate: function (e) {
60 if (this.mouseIsDown) this.touchEnd(e);
61 this.element.removeEventListener('mousedown', this.touchStart, true);
62 this.element.removeEventListener('mousemove', this.touchMove, true);
63 this.element.removeEventListener('mouseup', this.touchEnd, true);
64 this.element.removeEventListener('click', this.click, true);
68 if (e.synthetic) return;
73 touchStart: function (e) {
74 if (e.synthetic || /input|textarea/.test(e.target.tagName.toLowerCase())) return;
76 this.mouseIsDown = true;
81 this.fireTouchEvents('touchstart', e);
84 touchMove: function (e) {
85 if (e.synthetic) return;
90 this.move(e.clientX, e.clientY);
92 if (this.mouseIsDown) this.fireTouchEvents('touchmove', e);
95 touchEnd: function (e) {
96 if (e.synthetic) return;
98 this.mouseIsDown = false;
103 this.fireTouchEvents('touchend', e);
105 if (!this.target) return;
107 // Mobile Safari moves all the mouse events to fire after the touchend event.
108 this.target.dispatchEvent(this.createMouseEvent('mouseover', e));
109 this.target.dispatchEvent(this.createMouseEvent('mousemove', e));
110 this.target.dispatchEvent(this.createMouseEvent('mousedown', e));
113 fireTouchEvents: function (eventName, originalEvent) {
117 if (!this.target) return;
119 // Convert "ontouch*" properties and attributes to listeners.
120 var onEventName = 'on' + eventName;
122 if (onEventName in this.target) {
123 console.warn('Converting `' + onEventName + '` property to event listener.', this.target);
124 this.target.addEventListener(eventName, this.target[onEventName], false);
125 delete this.target[onEventName];
128 if (this.target.hasAttribute(onEventName)) {
129 console.warn('Converting `' + onEventName + '` attribute to event listener.', this.target);
130 var handler = new GLOBAL.Function('event', this.target.getAttribute(onEventName));
131 this.target.addEventListener(eventName, handler, false);
132 this.target.removeAttribute(onEventName);
135 // Set up a new event with the coordinates of the finger.
136 var touch = this.createMouseEvent(eventName, originalEvent);
140 // Figure out scale and rotation.
141 if (events.length > 1) {
142 var x = events[0].pageX - events[1].pageX;
143 var y = events[0].pageY - events[1].pageY;
145 var distance = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
146 var angle = Math.atan2(x, y) * (180 / Math.PI);
148 var gestureName = 'gesturechange';
150 if (eventName === 'touchstart') {
151 gestureName = 'gesturestart';
152 this.startDistance = distance;
153 this.startAngle = angle;
156 if (eventName === 'touchend') gestureName = 'gestureend';
158 events.forEach(function(event) {
159 var gesture = this.createMouseEvent.call(event._finger, gestureName, event);
160 gestures.push(gesture);
163 events.concat(gestures).forEach(function(event) {
164 event.scale = distance / this.startDistance;
165 event.rotation = this.startAngle - angle;
169 // Loop through the events array and fill in each touch array.
170 events.forEach(function(touch) {
171 touch.touches = events.filter(function(e) {
172 return ~e.type.indexOf('touch') && e.type !== 'touchend';
175 touch.changedTouches = events.filter(function(e) {
176 return ~e.type.indexOf('touch') && e._finger.target === touch._finger.target;
179 touch.targetTouches = touch.changedTouches.filter(function(e) {
180 return ~e.type.indexOf('touch') && e.type !== 'touchend';
184 // Then fire the events.
185 events.concat(gestures).forEach(function(event, i) {
186 event.identifier = i;
187 event._finger.target.dispatchEvent(event);
191 createMouseEvent: function (eventName, originalEvent) {
192 var e = document.createEvent('MouseEvent');
194 e.initMouseEvent(eventName, true, true,
195 originalEvent.view, originalEvent.detail,
196 this.x || originalEvent.screenX, this.y || originalEvent.screenY,
197 this.x || originalEvent.clientX, this.y || originalEvent.clientY,
198 originalEvent.ctrlKey, originalEvent.shiftKey,
199 originalEvent.altKey, originalEvent.metaKey,
200 originalEvent.button, this.target || originalEvent.relatedTarget
209 move: function (x, y) {
210 if (isNaN(x) || isNaN(y)) {
216 if (!this.mouseIsDown) {
217 this.target = document.elementFromPoint(x, y);