123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273 |
- // 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;
- }
- 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;
- }());
|