fingerblast.js 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. // FINGERBLAST.js
  2. // --------------
  3. // Adapted from phantom limb by Brian Cartensen
  4. /* jshint bitwise: false */
  5. /* global GLOBAL: true */
  6. (function () {
  7. 'use strict';
  8. function FingerBlast(element) {
  9. this.element = typeof element === 'string' ? document.querySelector(element) : element;
  10. if (this.element) {
  11. this.listen();
  12. }
  13. }
  14. FingerBlast.prototype = {
  15. x: NaN,
  16. y: NaN,
  17. startDistance: NaN,
  18. startAngle: NaN,
  19. mouseIsDown: false,
  20. listen: function () {
  21. var activate = this.activate.bind(this);
  22. var deactivate = this.deactivate.bind(this);
  23. function contains (element, ancestor) {
  24. var descendants;
  25. var index;
  26. var descendant;
  27. if (!element) {
  28. return;
  29. }
  30. if ('compareDocumentPosition' in ancestor) {
  31. return !!(ancestor.compareDocumentPosition(element) & 16);
  32. } else if ('contains' in ancestor) {
  33. return ancestor !== element && ancestor.contains(element);
  34. } else {
  35. for ((descendants = ancestor.getElementsByTagName('*')), index = 0; (descendant = descendants[index++]);) {
  36. if (descendant === element) {
  37. return true;
  38. }
  39. }
  40. return false;
  41. }
  42. }
  43. this.element.addEventListener('mouseover', function (e) {
  44. var target = e.relatedTarget;
  45. if (target !== this && !contains(target, this)) {
  46. activate();
  47. }
  48. });
  49. this.element.addEventListener('mouseout', function (e) {
  50. var target = e.relatedTarget;
  51. if (target !== this && !contains(target, this)) {
  52. deactivate(e);
  53. }
  54. });
  55. },
  56. activate: function () {
  57. if (this.active) {
  58. return;
  59. }
  60. this.element.addEventListener('mousedown', (this.touchStart = this.touchStart.bind(this)), true);
  61. this.element.addEventListener('mousemove', (this.touchMove = this.touchMove.bind(this)), true);
  62. this.element.addEventListener('mouseup', (this.touchEnd = this.touchEnd.bind(this)), true);
  63. this.element.addEventListener('click', (this.click = this.click.bind(this)), true);
  64. this.active = true;
  65. },
  66. deactivate: function (e) {
  67. this.active = false;
  68. if (this.mouseIsDown) {
  69. this.touchEnd(e);
  70. }
  71. this.element.removeEventListener('mousedown', this.touchStart, true);
  72. this.element.removeEventListener('mousemove', this.touchMove, true);
  73. this.element.removeEventListener('mouseup', this.touchEnd, true);
  74. this.element.removeEventListener('click', this.click, true);
  75. },
  76. click: function (e) {
  77. if (e.synthetic) {
  78. return;
  79. }
  80. e.preventDefault();
  81. e.stopPropagation();
  82. },
  83. touchStart: function (e) {
  84. if (e.synthetic || /input|textarea/.test(e.target.tagName.toLowerCase())) {
  85. return;
  86. }
  87. this.mouseIsDown = true;
  88. e.preventDefault();
  89. e.stopPropagation();
  90. this.fireTouchEvents('touchstart', e);
  91. },
  92. touchMove: function (e) {
  93. if (e.synthetic) {
  94. return;
  95. }
  96. e.preventDefault();
  97. e.stopPropagation();
  98. this.move(e.clientX, e.clientY);
  99. if (this.mouseIsDown) {
  100. this.fireTouchEvents('touchmove', e);
  101. }
  102. },
  103. touchEnd: function (e) {
  104. if (e.synthetic) {
  105. return;
  106. }
  107. this.mouseIsDown = false;
  108. e.preventDefault();
  109. e.stopPropagation();
  110. this.fireTouchEvents('touchend', e);
  111. if (!this.target) {
  112. return;
  113. }
  114. // Mobile Safari moves all the mouse events to fire after the touchend event.
  115. this.target.dispatchEvent(this.createMouseEvent('mouseover', e));
  116. this.target.dispatchEvent(this.createMouseEvent('mousemove', e));
  117. this.target.dispatchEvent(this.createMouseEvent('mousedown', e));
  118. },
  119. fireTouchEvents: function (eventName, originalEvent) {
  120. var events = [];
  121. var gestures = [];
  122. if (!this.target) {
  123. return;
  124. }
  125. // Convert 'ontouch*' properties and attributes to listeners.
  126. var onEventName = 'on' + eventName;
  127. if (onEventName in this.target) {
  128. console.warn('Converting `' + onEventName + '` property to event listener.', this.target);
  129. this.target.addEventListener(eventName, this.target[onEventName], false);
  130. delete this.target[onEventName];
  131. }
  132. if (this.target.hasAttribute(onEventName)) {
  133. console.warn('Converting `' + onEventName + '` attribute to event listener.', this.target);
  134. var handler = new GLOBAL.Function('event', this.target.getAttribute(onEventName));
  135. this.target.addEventListener(eventName, handler, false);
  136. this.target.removeAttribute(onEventName);
  137. }
  138. // Set up a new event with the coordinates of the finger.
  139. var touch = this.createMouseEvent(eventName, originalEvent);
  140. events.push(touch);
  141. // Figure out scale and rotation.
  142. if (events.length > 1) {
  143. var x = events[0].pageX - events[1].pageX;
  144. 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);
  147. var gestureName = 'gesturechange';
  148. if (eventName === 'touchstart') {
  149. gestureName = 'gesturestart';
  150. this.startDistance = distance;
  151. this.startAngle = angle;
  152. }
  153. if (eventName === 'touchend') {
  154. gestureName = 'gestureend';
  155. }
  156. events.forEach(function(event) {
  157. var gesture = this.createMouseEvent.call(event._finger, gestureName, event);
  158. gestures.push(gesture);
  159. }.bind(this));
  160. events.concat(gestures).forEach(function(event) {
  161. event.scale = distance / this.startDistance;
  162. event.rotation = this.startAngle - angle;
  163. });
  164. }
  165. // Loop through the events array and fill in each touch array.
  166. events.forEach(function(touch) {
  167. touch.touches = events.filter(function(e) {
  168. return ~e.type.indexOf('touch') && e.type !== 'touchend';
  169. });
  170. touch.changedTouches = events.filter(function(e) {
  171. return ~e.type.indexOf('touch') && e._finger.target === touch._finger.target;
  172. });
  173. touch.targetTouches = touch.changedTouches.filter(function(e) {
  174. return ~e.type.indexOf('touch') && e.type !== 'touchend';
  175. });
  176. });
  177. // Then fire the events.
  178. events.concat(gestures).forEach(function(event, i) {
  179. event.identifier = i;
  180. event._finger.target.dispatchEvent(event);
  181. });
  182. },
  183. createMouseEvent: function (eventName, originalEvent) {
  184. var e = new MouseEvent(eventName, {
  185. view : window,
  186. detail : originalEvent.detail,
  187. bubbles : true,
  188. cancelable : true,
  189. target : this.target || originalEvent.relatedTarget,
  190. clientX : this.x || originalEvent.clientX,
  191. clientY : this.y || originalEvent.clientY,
  192. screenX : this.x || originalEvent.screenX,
  193. screenY : this.y || originalEvent.screenY,
  194. ctrlKey : originalEvent.ctrlKey,
  195. shiftKey : originalEvent.shiftKey,
  196. altKey : originalEvent.altKey,
  197. metaKey : originalEvent.metaKey,
  198. button : originalEvent.button
  199. });
  200. e.synthetic = true;
  201. e._finger = this;
  202. return e;
  203. },
  204. move: function (x, y) {
  205. if (isNaN(x) || isNaN(y)) {
  206. this.target = null;
  207. } else {
  208. this.x = x;
  209. this.y = y;
  210. if (!this.mouseIsDown) {
  211. this.target = document.elementFromPoint(x, y);
  212. }
  213. }
  214. }
  215. };
  216. window.FingerBlast = FingerBlast;
  217. }());