// FINGERBLAST.js // -------------- // Adapted from phantom limb by Brian Cartensen /* jshint bitwise: false */ /* global GLOBAL: true */ (function () { 'use strict'; function FingerBlast (element) { this.element = typeof element === 'string' ? document.querySelector(element) : element; if (this.element) { this.listen(); } } FingerBlast.prototype = { x: NaN, y: NaN, startDistance: NaN, startAngle: NaN, mouseIsDown: false, listen: function () { var activate = this.activate.bind(this); var deactivate = this.deactivate.bind(this); function contains (element, ancestor) { var descendants; var index; var descendant; if (!element) { return false; } if ('compareDocumentPosition' in ancestor) { return !!(ancestor.compareDocumentPosition(element) & 16); } else if ('contains' in ancestor) { return ancestor !== element && ancestor.contains(element); } else { for ((descendants = ancestor.getElementsByTagName('*')), index = 0; (descendant = descendants[index++]);) { if (descendant === element) { return true; } } return false; } } this.element.addEventListener('mouseover', function (e) { var target = e.relatedTarget; if (target !== this && !contains(target, this)) { activate(); } }); this.element.addEventListener('mouseout', function (e) { var target = e.relatedTarget; if (target !== this && !contains(target, this)) { deactivate(e); } }); }, activate: function () { if (this.active) { return; } this.element.addEventListener('mousedown', (this.touchStart = this.touchStart.bind(this)), true); this.element.addEventListener('mousemove', (this.touchMove = this.touchMove.bind(this)), true); this.element.addEventListener('mouseup', (this.touchEnd = this.touchEnd.bind(this)), true); this.element.addEventListener('click', (this.click = this.click.bind(this)), true); this.active = true; }, deactivate: function (e) { this.active = false; if (this.mouseIsDown) { this.touchEnd(e); } this.element.removeEventListener('mousedown', this.touchStart, true); this.element.removeEventListener('mousemove', this.touchMove, true); this.element.removeEventListener('mouseup', this.touchEnd, true); this.element.removeEventListener('click', this.click, true); }, click: function (e) { if (e.synthetic) { return; } e.preventDefault(); e.stopPropagation(); }, touchStart: function (e) { if (e.synthetic || /input|textarea/.test(e.target.tagName.toLowerCase())) { return; } this.mouseIsDown = true; e.preventDefault(); e.stopPropagation(); this.fireTouchEvents('touchstart', e); }, touchMove: function (e) { if (e.synthetic) { return; } e.preventDefault(); e.stopPropagation(); this.move(e.clientX, e.clientY); if (this.mouseIsDown) { this.fireTouchEvents('touchmove', e); } }, touchEnd: function (e) { if (e.synthetic) { return; } this.mouseIsDown = false; e.preventDefault(); e.stopPropagation(); this.fireTouchEvents('touchend', e); if (!this.target) { return; } // Mobile Safari moves all the mouse events to fire after the touchend event. this.target.dispatchEvent(this.createMouseEvent('mouseover', e)); this.target.dispatchEvent(this.createMouseEvent('mousemove', e)); this.target.dispatchEvent(this.createMouseEvent('mousedown', e)); }, fireTouchEvents: function (eventName, originalEvent) { var events = []; var gestures = []; if (!this.target) { return; } // Convert 'ontouch*' properties and attributes to listeners. var onEventName = 'on' + eventName; if (onEventName in this.target) { console.warn('Converting `' + onEventName + '` property to event listener.', this.target); this.target.addEventListener(eventName, this.target[onEventName], false); delete this.target[onEventName]; } if (this.target.hasAttribute(onEventName)) { console.warn('Converting `' + onEventName + '` attribute to event listener.', this.target); var handler = new GLOBAL.Function('event', this.target.getAttribute(onEventName)); this.target.addEventListener(eventName, handler, false); this.target.removeAttribute(onEventName); } // Set up a new event with the coordinates of the finger. var touch = this.createMouseEvent(eventName, originalEvent); events.push(touch); // Figure out scale and rotation. if (events.length > 1) { var x = events[0].pageX - events[1].pageX; var y = events[0].pageY - events[1].pageY; var distance = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); var angle = Math.atan2(x, y) * (180 / Math.PI); var gestureName = 'gesturechange'; if (eventName === 'touchstart') { gestureName = 'gesturestart'; this.startDistance = distance; this.startAngle = angle; } if (eventName === 'touchend') { gestureName = 'gestureend'; } events.forEach(function (event) { var gesture = this.createMouseEvent.call(event._finger, gestureName, event); gestures.push(gesture); }.bind(this)); events.concat(gestures).forEach(function (event) { event.scale = distance / this.startDistance; event.rotation = this.startAngle - angle; }); } // Loop through the events array and fill in each touch array. events.forEach(function (touch) { touch.touches = events.filter(function (e) { return ~e.type.indexOf('touch') && e.type !== 'touchend'; }); touch.changedTouches = events.filter(function (e) { return ~e.type.indexOf('touch') && e._finger.target === touch._finger.target; }); touch.targetTouches = touch.changedTouches.filter(function (e) { return ~e.type.indexOf('touch') && e.type !== 'touchend'; }); }); // Then fire the events. events.concat(gestures).forEach(function (event, i) { event.identifier = i; event._finger.target.dispatchEvent(event); }); }, createMouseEvent: function (eventName, originalEvent) { var e = new MouseEvent(eventName, { view : window, detail : originalEvent.detail, bubbles : true, cancelable : true, target : this.target || originalEvent.relatedTarget, clientX : this.x || originalEvent.clientX, clientY : this.y || originalEvent.clientY, screenX : this.x || originalEvent.screenX, screenY : this.y || originalEvent.screenY, ctrlKey : originalEvent.ctrlKey, shiftKey : originalEvent.shiftKey, altKey : originalEvent.altKey, metaKey : originalEvent.metaKey, button : originalEvent.button }); e.synthetic = true; e._finger = this; return e; }, move: function (x, y) { if (isNaN(x) || isNaN(y)) { this.target = null; } else { this.x = x; this.y = y; if (!this.mouseIsDown) { this.target = document.elementFromPoint(x, y); } } } }; window.FingerBlast = FingerBlast; }());