touch-emulator.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. (function (window, document, exportName, undefined) {
  2. "use strict";
  3. var isMultiTouch = false;
  4. var multiTouchStartPos;
  5. var eventTarget;
  6. var touchElements = {};
  7. // polyfills
  8. if (!document.createTouch) {
  9. document.createTouch = function (view, target, identifier, pageX, pageY, screenX, screenY, clientX, clientY) {
  10. // auto set
  11. if (clientX == undefined || clientY == undefined) {
  12. clientX = pageX - window.pageXOffset;
  13. clientY = pageY - window.pageYOffset;
  14. }
  15. return new Touch(target, identifier, {
  16. pageX: pageX,
  17. pageY: pageY,
  18. screenX: screenX,
  19. screenY: screenY,
  20. clientX: clientX,
  21. clientY: clientY
  22. });
  23. };
  24. }
  25. if (!document.createTouchList) {
  26. document.createTouchList = function () {
  27. var touchList = new TouchList();
  28. for (var i = 0; i < arguments.length; i++) {
  29. touchList[i] = arguments[i];
  30. }
  31. touchList.length = arguments.length;
  32. return touchList;
  33. };
  34. }
  35. /**
  36. * create an touch point
  37. * @constructor
  38. * @param target
  39. * @param identifier
  40. * @param pos
  41. * @param deltaX
  42. * @param deltaY
  43. * @returns {Object} touchPoint
  44. */
  45. function Touch(target, identifier, pos, deltaX, deltaY) {
  46. deltaX = deltaX || 0;
  47. deltaY = deltaY || 0;
  48. this.identifier = identifier;
  49. this.target = target;
  50. this.clientX = pos.clientX + deltaX;
  51. this.clientY = pos.clientY + deltaY;
  52. this.screenX = pos.screenX + deltaX;
  53. this.screenY = pos.screenY + deltaY;
  54. this.pageX = pos.pageX + deltaX;
  55. this.pageY = pos.pageY + deltaY;
  56. }
  57. /**
  58. * create empty touchlist with the methods
  59. * @constructor
  60. * @returns touchList
  61. */
  62. function TouchList() {
  63. var touchList = [];
  64. touchList.item = function (index) {
  65. return this[index] || null;
  66. };
  67. // specified by Mozilla
  68. touchList.identifiedTouch = function (id) {
  69. return this[id + 1] || null;
  70. };
  71. return touchList;
  72. }
  73. /**
  74. * Simple trick to fake touch event support
  75. * this is enough for most libraries like Modernizr and Hammer
  76. */
  77. function fakeTouchSupport() {
  78. var objs = [window, document.documentElement];
  79. var props = ['ontouchstart', 'ontouchmove', 'ontouchcancel', 'ontouchend'];
  80. for (var o = 0; o < objs.length; o++) {
  81. for (var p = 0; p < props.length; p++) {
  82. if (objs[o] && objs[o][props[p]] == undefined) {
  83. objs[o][props[p]] = null;
  84. }
  85. }
  86. }
  87. }
  88. /**
  89. * we don't have to emulate on a touch device
  90. * @returns {boolean}
  91. */
  92. function hasTouchSupport() {
  93. return ("ontouchstart" in window) || // touch events
  94. (window.Modernizr && window.Modernizr.touch) || // modernizr
  95. (navigator.msMaxTouchPoints || navigator.maxTouchPoints) > 2; // pointer events
  96. }
  97. /**
  98. * disable mouseevents on the page
  99. * @param ev
  100. */
  101. function preventMouseEvents(ev) {
  102. // 注释启用默认事件
  103. // ev.preventDefault();
  104. // ev.stopPropagation();
  105. }
  106. /**
  107. * only trigger touches when the left mousebutton has been pressed
  108. * @param touchType
  109. * @returns {Function}
  110. */
  111. function onMouse(touchType) {
  112. return function (ev) {
  113. // prevent mouse events
  114. preventMouseEvents(ev);
  115. if (ev.which !== 1) {
  116. return;
  117. }
  118. // The EventTarget on which the touch point started when it was first placed on the surface,
  119. // even if the touch point has since moved outside the interactive area of that element.
  120. // also, when the target doesnt exist anymore, we update it
  121. if (ev.type == 'mousedown' || !eventTarget || (eventTarget && !eventTarget.dispatchEvent)) {
  122. eventTarget = ev.target;
  123. }
  124. // shiftKey has been lost, so trigger a touchend
  125. if (isMultiTouch && !ev.shiftKey) {
  126. triggerTouch('touchend', ev);
  127. isMultiTouch = false;
  128. }
  129. triggerTouch(touchType, ev);
  130. // we're entering the multi-touch mode!
  131. if (!isMultiTouch && ev.shiftKey) {
  132. isMultiTouch = true;
  133. multiTouchStartPos = {
  134. pageX: ev.pageX,
  135. pageY: ev.pageY,
  136. clientX: ev.clientX,
  137. clientY: ev.clientY,
  138. screenX: ev.screenX,
  139. screenY: ev.screenY
  140. };
  141. triggerTouch('touchstart', ev);
  142. }
  143. // reset
  144. if (ev.type == 'mouseup') {
  145. multiTouchStartPos = null;
  146. isMultiTouch = false;
  147. eventTarget = null;
  148. }
  149. }
  150. }
  151. /**
  152. * trigger a touch event
  153. * @param eventName
  154. * @param mouseEv
  155. */
  156. function triggerTouch(eventName, mouseEv) {
  157. var touchEvent = document.createEvent('Event');
  158. touchEvent.initEvent(eventName, true, true);
  159. touchEvent.altKey = mouseEv.altKey;
  160. touchEvent.ctrlKey = mouseEv.ctrlKey;
  161. touchEvent.metaKey = mouseEv.metaKey;
  162. touchEvent.shiftKey = mouseEv.shiftKey;
  163. touchEvent.touches = getActiveTouches(mouseEv, eventName);
  164. touchEvent.targetTouches = getActiveTouches(mouseEv, eventName);
  165. touchEvent.changedTouches = getChangedTouches(mouseEv, eventName);
  166. eventTarget.dispatchEvent(touchEvent);
  167. }
  168. /**
  169. * create a touchList based on the mouse event
  170. * @param mouseEv
  171. * @returns {TouchList}
  172. */
  173. function createTouchList(mouseEv) {
  174. var touchList = new TouchList();
  175. if (isMultiTouch) {
  176. var f = TouchEmulator.multiTouchOffset;
  177. var deltaX = multiTouchStartPos.pageX - mouseEv.pageX;
  178. var deltaY = multiTouchStartPos.pageY - mouseEv.pageY;
  179. touchList.push(new Touch(eventTarget, 1, multiTouchStartPos, (deltaX * -1) - f, (deltaY * -1) + f));
  180. touchList.push(new Touch(eventTarget, 2, multiTouchStartPos, deltaX + f, deltaY - f));
  181. } else {
  182. touchList.push(new Touch(eventTarget, 1, mouseEv, 0, 0));
  183. }
  184. return touchList;
  185. }
  186. /**
  187. * receive all active touches
  188. * @param mouseEv
  189. * @returns {TouchList}
  190. */
  191. function getActiveTouches(mouseEv, eventName) {
  192. // empty list
  193. if (mouseEv.type == 'mouseup') {
  194. return new TouchList();
  195. }
  196. var touchList = createTouchList(mouseEv);
  197. if (isMultiTouch && mouseEv.type != 'mouseup' && eventName == 'touchend') {
  198. touchList.splice(1, 1);
  199. }
  200. return touchList;
  201. }
  202. /**
  203. * receive a filtered set of touches with only the changed pointers
  204. * @param mouseEv
  205. * @param eventName
  206. * @returns {TouchList}
  207. */
  208. function getChangedTouches(mouseEv, eventName) {
  209. var touchList = createTouchList(mouseEv);
  210. // we only want to return the added/removed item on multitouch
  211. // which is the second pointer, so remove the first pointer from the touchList
  212. //
  213. // but when the mouseEv.type is mouseup, we want to send all touches because then
  214. // no new input will be possible
  215. if (isMultiTouch && mouseEv.type != 'mouseup' &&
  216. (eventName == 'touchstart' || eventName == 'touchend')) {
  217. touchList.splice(0, 1);
  218. }
  219. return touchList;
  220. }
  221. /**
  222. * show the touchpoints on the screen
  223. */
  224. function showTouches(ev) {
  225. var touch, i, el, styles;
  226. // first all visible touches
  227. for (i = 0; i < ev.touches.length; i++) {
  228. touch = ev.touches[i];
  229. el = touchElements[touch.identifier];
  230. if (!el) {
  231. el = touchElements[touch.identifier] = document.createElement("div");
  232. document.body.appendChild(el);
  233. }
  234. styles = TouchEmulator.template(touch);
  235. for (var prop in styles) {
  236. el.style[prop] = styles[prop];
  237. }
  238. }
  239. // remove all ended touches
  240. if (ev.type == 'touchend' || ev.type == 'touchcancel') {
  241. for (i = 0; i < ev.changedTouches.length; i++) {
  242. touch = ev.changedTouches[i];
  243. el = touchElements[touch.identifier];
  244. if (el) {
  245. el.parentNode.removeChild(el);
  246. delete touchElements[touch.identifier];
  247. }
  248. }
  249. }
  250. }
  251. /**
  252. * TouchEmulator initializer
  253. */
  254. function TouchEmulator() {
  255. if (hasTouchSupport()) {
  256. return;
  257. }
  258. fakeTouchSupport();
  259. window.addEventListener("mousedown", onMouse('touchstart'), true);
  260. window.addEventListener("mousemove", onMouse('touchmove'), true);
  261. window.addEventListener("mouseup", onMouse('touchend'), true);
  262. window.addEventListener("mouseenter", preventMouseEvents, true);
  263. window.addEventListener("mouseleave", preventMouseEvents, true);
  264. window.addEventListener("mouseout", preventMouseEvents, true);
  265. window.addEventListener("mouseover", preventMouseEvents, true);
  266. // it uses itself!
  267. window.addEventListener("touchstart", showTouches, true);
  268. window.addEventListener("touchmove", showTouches, true);
  269. window.addEventListener("touchend", showTouches, true);
  270. window.addEventListener("touchcancel", showTouches, true);
  271. }
  272. // start distance when entering the multitouch mode
  273. TouchEmulator.multiTouchOffset = 75;
  274. /**
  275. * css template for the touch rendering
  276. * @param touch
  277. * @returns object
  278. */
  279. TouchEmulator.template = function (touch) {
  280. var size = 0;
  281. var transform = 'translate(' + (touch.clientX - (size / 2)) + 'px, ' + (touch.clientY - (size / 2)) + 'px)';
  282. return {
  283. position: 'fixed',
  284. left: 0,
  285. top: 0,
  286. background: '#fff',
  287. border: 'solid 1px #999',
  288. opacity: .6,
  289. borderRadius: '100%',
  290. height: size + 'px',
  291. width: size + 'px',
  292. padding: 0,
  293. margin: 0,
  294. display: 'block',
  295. overflow: 'hidden',
  296. pointerEvents: 'none',
  297. webkitUserSelect: 'none',
  298. mozUserSelect: 'none',
  299. userSelect: 'none',
  300. webkitTransform: transform,
  301. mozTransform: transform,
  302. transform: transform,
  303. zIndex: 100
  304. }
  305. };
  306. // export
  307. if (typeof define == "function" && define.amd) {
  308. define(function () {
  309. return TouchEmulator;
  310. });
  311. } else if (typeof module != "undefined" && module.exports) {
  312. module.exports = TouchEmulator;
  313. } else {
  314. window[exportName] = TouchEmulator;
  315. }
  316. })(window, document, "TouchEmulator");