alloyfinger.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. /* AlloyFinger v0.1.15
  2. * By dntzhang
  3. * Github: https://github.com/AlloyTeam/AlloyFinger
  4. * 监听触摸的工具类
  5. */
  6. (function () {
  7. // 获取两点距离
  8. function getLen(v) {
  9. return Math.sqrt(v.x * v.x + v.y * v.y);
  10. }
  11. function dot(v1, v2) {
  12. return v1.x * v2.x + v1.y * v2.y;
  13. }
  14. // 获取角度
  15. function getAngle(v1, v2) {
  16. var mr = getLen(v1) * getLen(v2);
  17. if (mr === 0) return 0;
  18. var r = dot(v1, v2) / mr;
  19. if (r > 1) r = 1;
  20. return Math.acos(r);
  21. }
  22. function cross(v1, v2) {
  23. return v1.x * v2.y - v2.x * v1.y;
  24. }
  25. function getRotateAngle(v1, v2) {
  26. var angle = getAngle(v1, v2);
  27. if (cross(v1, v2) > 0) {
  28. angle *= -1;
  29. }
  30. return (angle * 180) / Math.PI;
  31. }
  32. var HandlerAdmin = function (el) {
  33. this.handlers = [];
  34. this.el = el;
  35. };
  36. HandlerAdmin.prototype.add = function (handler) {
  37. this.handlers.push(handler);
  38. };
  39. HandlerAdmin.prototype.del = function (handler) {
  40. if (!handler) this.handlers = [];
  41. for (var i = this.handlers.length; i >= 0; i--) {
  42. if (this.handlers[i] === handler) {
  43. this.handlers.splice(i, 1);
  44. }
  45. }
  46. };
  47. HandlerAdmin.prototype.dispatch = function () {
  48. for (var i = 0, len = this.handlers.length; i < len; i++) {
  49. var handler = this.handlers[i];
  50. if (typeof handler === "function") handler.apply(this.el, arguments);
  51. }
  52. };
  53. function wrapFunc(el, handler) {
  54. var handlerAdmin = new HandlerAdmin(el);
  55. handlerAdmin.add(handler);
  56. return handlerAdmin;
  57. }
  58. var AlloyFinger = function (el, option) {
  59. this.element = typeof el == "string" ? document.querySelector(el) : el;
  60. this.start = this.start.bind(this);
  61. this.move = this.move.bind(this);
  62. this.end = this.end.bind(this);
  63. this.cancel = this.cancel.bind(this);
  64. this.element.addEventListener("touchstart", this.start, false);
  65. this.element.addEventListener("touchmove", this.move, false);
  66. this.element.addEventListener("touchend", this.end, false);
  67. this.element.addEventListener("touchcancel", this.cancel, false);
  68. this.preV = {
  69. x: null,
  70. y: null,
  71. };
  72. this.pinchStartLen = null;
  73. this.zoom = 1;
  74. this.isDoubleTap = false;
  75. var noop = function () {};
  76. this.rotate = wrapFunc(this.element, option.rotate || noop);
  77. this.touchStart = wrapFunc(this.element, option.touchStart || noop);
  78. this.multipointStart = wrapFunc(
  79. this.element,
  80. option.multipointStart || noop
  81. );
  82. this.multipointEnd = wrapFunc(this.element, option.multipointEnd || noop);
  83. this.pinch = wrapFunc(this.element, option.pinch || noop);
  84. this.swipe = wrapFunc(this.element, option.swipe || noop);
  85. this.tap = wrapFunc(this.element, option.tap || noop);
  86. this.doubleTap = wrapFunc(this.element, option.doubleTap || noop);
  87. this.longTap = wrapFunc(this.element, option.longTap || noop);
  88. this.singleTap = wrapFunc(this.element, option.singleTap || noop);
  89. this.pressMove = wrapFunc(this.element, option.pressMove || noop);
  90. this.twoFingerPressMove = wrapFunc(
  91. this.element,
  92. option.twoFingerPressMove || noop
  93. );
  94. this.touchMove = wrapFunc(this.element, option.touchMove || noop);
  95. this.touchEnd = wrapFunc(this.element, option.touchEnd || noop);
  96. this.touchCancel = wrapFunc(this.element, option.touchCancel || noop);
  97. this._cancelAllHandler = this.cancelAll.bind(this);
  98. window.addEventListener("scroll", this._cancelAllHandler);
  99. this.delta = null;
  100. this.last = null;
  101. this.now = null;
  102. this.tapTimeout = null;
  103. this.singleTapTimeout = null;
  104. this.longTapTimeout = null;
  105. this.swipeTimeout = null;
  106. this.x1 = this.x2 = this.y1 = this.y2 = null;
  107. this.preTapPosition = {
  108. x: null,
  109. y: null,
  110. };
  111. };
  112. AlloyFinger.prototype = {
  113. start: function (evt) {
  114. if (!evt.touches) return;
  115. this.now = Date.now();
  116. this.x1 = evt.touches[0].pageX;
  117. this.y1 = evt.touches[0].pageY;
  118. this.delta = this.now - (this.last || this.now);
  119. this.touchStart.dispatch(evt, this.element);
  120. if (this.preTapPosition.x !== null) {
  121. this.isDoubleTap =
  122. this.delta > 0 &&
  123. this.delta <= 250 &&
  124. Math.abs(this.preTapPosition.x - this.x1) < 30 &&
  125. Math.abs(this.preTapPosition.y - this.y1) < 30;
  126. if (this.isDoubleTap) clearTimeout(this.singleTapTimeout);
  127. }
  128. this.preTapPosition.x = this.x1;
  129. this.preTapPosition.y = this.y1;
  130. this.last = this.now;
  131. var preV = this.preV,
  132. len = evt.touches.length;
  133. if (len > 1) {
  134. this._cancelLongTap();
  135. this._cancelSingleTap();
  136. var v = {
  137. x: evt.touches[1].pageX - this.x1,
  138. y: evt.touches[1].pageY - this.y1,
  139. };
  140. preV.x = v.x;
  141. preV.y = v.y;
  142. this.pinchStartLen = getLen(preV);
  143. this.multipointStart.dispatch(evt, this.element);
  144. }
  145. this._preventTap = false;
  146. this.longTapTimeout = setTimeout(
  147. function () {
  148. this.longTap.dispatch(evt, this.element);
  149. this._preventTap = true;
  150. }.bind(this),
  151. 750
  152. );
  153. },
  154. move: function (evt) {
  155. if (!evt.touches) return;
  156. var preV = this.preV,
  157. len = evt.touches.length,
  158. currentX = evt.touches[0].pageX,
  159. currentY = evt.touches[0].pageY;
  160. this.isDoubleTap = false;
  161. if (len > 1) {
  162. var sCurrentX = evt.touches[1].pageX,
  163. sCurrentY = evt.touches[1].pageY;
  164. var v = {
  165. x: evt.touches[1].pageX - currentX,
  166. y: evt.touches[1].pageY - currentY,
  167. };
  168. if (preV.x !== null) {
  169. if (this.pinchStartLen > 0) {
  170. evt.zoom = getLen(v) / this.pinchStartLen;
  171. this.pinch.dispatch(evt, this.element);
  172. }
  173. evt.angle = getRotateAngle(v, preV);
  174. this.rotate.dispatch(evt, this.element);
  175. }
  176. preV.x = v.x;
  177. preV.y = v.y;
  178. if (this.x2 !== null && this.sx2 !== null) {
  179. evt.deltaX = (currentX - this.x2 + sCurrentX - this.sx2) / 2;
  180. evt.deltaY = (currentY - this.y2 + sCurrentY - this.sy2) / 2;
  181. } else {
  182. evt.deltaX = 0;
  183. evt.deltaY = 0;
  184. }
  185. this.twoFingerPressMove.dispatch(evt, this.element);
  186. this.sx2 = sCurrentX;
  187. this.sy2 = sCurrentY;
  188. } else {
  189. if (this.x2 !== null) {
  190. evt.deltaX = currentX - this.x2;
  191. evt.deltaY = currentY - this.y2;
  192. //move事件中添加对当前触摸点到初始触摸点的判断,
  193. //如果曾经大于过某个距离(比如10),就认为是移动到某个地方又移回来,应该不再触发tap事件才对。
  194. var movedX = Math.abs(this.x1 - this.x2),
  195. movedY = Math.abs(this.y1 - this.y2);
  196. if (movedX > 10 || movedY > 10) {
  197. this._preventTap = true;
  198. }
  199. } else {
  200. evt.deltaX = 0;
  201. evt.deltaY = 0;
  202. }
  203. this.pressMove.dispatch(evt, this.element);
  204. }
  205. this.touchMove.dispatch(evt, this.element);
  206. this._cancelLongTap();
  207. this.x2 = currentX;
  208. this.y2 = currentY;
  209. if (len > 1) {
  210. evt.preventDefault();
  211. }
  212. },
  213. end: function (evt) {
  214. if (!evt.changedTouches) return;
  215. this._cancelLongTap();
  216. var self = this;
  217. if (evt.touches.length < 2) {
  218. this.multipointEnd.dispatch(evt, this.element);
  219. this.sx2 = this.sy2 = null;
  220. }
  221. //swipe
  222. if (
  223. (this.x2 && Math.abs(this.x1 - this.x2) > 30) ||
  224. (this.y2 && Math.abs(this.y1 - this.y2) > 30)
  225. ) {
  226. evt.direction = this._swipeDirection(
  227. this.x1,
  228. this.x2,
  229. this.y1,
  230. this.y2
  231. );
  232. this.swipeTimeout = setTimeout(function () {
  233. self.swipe.dispatch(evt, self.element);
  234. }, 0);
  235. } else {
  236. this.tapTimeout = setTimeout(function () {
  237. if (!self._preventTap) {
  238. self.tap.dispatch(evt, self.element);
  239. }
  240. // trigger double tap immediately
  241. if (self.isDoubleTap) {
  242. self.doubleTap.dispatch(evt, self.element);
  243. self.isDoubleTap = false;
  244. }
  245. }, 0);
  246. if (!self.isDoubleTap) {
  247. self.singleTapTimeout = setTimeout(function () {
  248. self.singleTap.dispatch(evt, self.element);
  249. }, 250);
  250. }
  251. }
  252. this.touchEnd.dispatch(evt, this.element);
  253. this.preV.x = 0;
  254. this.preV.y = 0;
  255. this.zoom = 1;
  256. this.pinchStartLen = null;
  257. this.x1 = this.x2 = this.y1 = this.y2 = null;
  258. },
  259. cancelAll: function () {
  260. this._preventTap = true;
  261. clearTimeout(this.singleTapTimeout);
  262. clearTimeout(this.tapTimeout);
  263. clearTimeout(this.longTapTimeout);
  264. clearTimeout(this.swipeTimeout);
  265. },
  266. cancel: function (evt) {
  267. this.cancelAll();
  268. this.touchCancel.dispatch(evt, this.element);
  269. },
  270. _cancelLongTap: function () {
  271. clearTimeout(this.longTapTimeout);
  272. },
  273. _cancelSingleTap: function () {
  274. clearTimeout(this.singleTapTimeout);
  275. },
  276. _swipeDirection: function (x1, x2, y1, y2) {
  277. return Math.abs(x1 - x2) >= Math.abs(y1 - y2)
  278. ? x1 - x2 > 0
  279. ? "Left"
  280. : "Right"
  281. : y1 - y2 > 0
  282. ? "Up"
  283. : "Down";
  284. },
  285. on: function (evt, handler) {
  286. if (this[evt]) {
  287. this[evt].add(handler);
  288. }
  289. },
  290. off: function (evt, handler) {
  291. if (this[evt]) {
  292. this[evt].del(handler);
  293. }
  294. },
  295. destroy: function () {
  296. if (this.singleTapTimeout) clearTimeout(this.singleTapTimeout);
  297. if (this.tapTimeout) clearTimeout(this.tapTimeout);
  298. if (this.longTapTimeout) clearTimeout(this.longTapTimeout);
  299. if (this.swipeTimeout) clearTimeout(this.swipeTimeout);
  300. this.element.removeEventListener("touchstart", this.start);
  301. this.element.removeEventListener("touchmove", this.move);
  302. this.element.removeEventListener("touchend", this.end);
  303. this.element.removeEventListener("touchcancel", this.cancel);
  304. this.rotate.del();
  305. this.touchStart.del();
  306. this.multipointStart.del();
  307. this.multipointEnd.del();
  308. this.pinch.del();
  309. this.swipe.del();
  310. this.tap.del();
  311. this.doubleTap.del();
  312. this.longTap.del();
  313. this.singleTap.del();
  314. this.pressMove.del();
  315. this.twoFingerPressMove.del();
  316. this.touchMove.del();
  317. this.touchEnd.del();
  318. this.touchCancel.del();
  319. this.preV =
  320. this.pinchStartLen =
  321. this.zoom =
  322. this.isDoubleTap =
  323. this.delta =
  324. this.last =
  325. this.now =
  326. this.tapTimeout =
  327. this.singleTapTimeout =
  328. this.longTapTimeout =
  329. this.swipeTimeout =
  330. this.x1 =
  331. this.x2 =
  332. this.y1 =
  333. this.y2 =
  334. this.preTapPosition =
  335. this.rotate =
  336. this.touchStart =
  337. this.multipointStart =
  338. this.multipointEnd =
  339. this.pinch =
  340. this.swipe =
  341. this.tap =
  342. this.doubleTap =
  343. this.longTap =
  344. this.singleTap =
  345. this.pressMove =
  346. this.touchMove =
  347. this.touchEnd =
  348. this.touchCancel =
  349. this.twoFingerPressMove =
  350. null;
  351. window.removeEventListener("scroll", this._cancelAllHandler);
  352. return null;
  353. },
  354. };
  355. if (typeof module !== "undefined" && typeof exports === "object") {
  356. module.exports = AlloyFinger;
  357. } else {
  358. window.AlloyFinger = AlloyFinger;
  359. }
  360. })();