gesture.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. var SUPPORT_TOUCH = "ontouchstart" in window;
  2. var touchEvents = [
  3. ["touchstart", "start"],
  4. ["touchmove", "move"],
  5. ["touchend", "end"],
  6. ["touchcancel", "cancel"]
  7. ];
  8. var mouseEvents = [
  9. ["mousedown", "start"],
  10. ["mouseup", "end"],
  11. ["mousemove", "move"]
  12. ];
  13. var events = SUPPORT_TOUCH ? touchEvents : mouseEvents;
  14. function forEach(obj, callback) {
  15. for (key in obj) {
  16. if (obj.hasOwnProperty(key)) callback(obj[key], key, obj);
  17. }
  18. }
  19. /*
  20. options{
  21. leading:false 表示禁用第一次执行
  22. trailing: false 表示禁用停止触发的回调
  23. }
  24. */
  25. function throttle(func, wait, options) {
  26. var timeout, context, args, result;
  27. var previous = 0;
  28. if (!options) options = {};
  29. var later = function() {
  30. previous = options.leading === false ? 0 : new Date().getTime();
  31. timeout = null;
  32. func.apply(context, args);
  33. if (!timeout) context = args = null;
  34. };
  35. var throttled = function() {
  36. var now = new Date().getTime();
  37. if (!previous && options.leading === false) previous = now;
  38. var remaining = wait - (now - previous);
  39. context = this;
  40. args = arguments;
  41. // 如果没有剩余的时间了或者你改了系统时间
  42. if (remaining <= 0 || remaining > wait) {
  43. if (timeout) {
  44. clearTimeout(timeout);
  45. timeout = null;
  46. }
  47. previous = now;
  48. func.apply(context, args);
  49. if (!timeout) context = args = null;
  50. } else if (!timeout && options.trailing !== false) {
  51. timeout = setTimeout(later, remaining);
  52. }
  53. };
  54. return throttled;
  55. }
  56. /*
  57. @params options : {
  58. string name
  59. function start ,
  60. function move,
  61. function end,
  62. function cancel
  63. }
  64. */
  65. function Gesture(options) {
  66. var _this = this;
  67. forEach(options, function(v, k) {
  68. _this[k] = v;
  69. });
  70. if (this.init) this.init();
  71. events.forEach(function(ev) {
  72. document.addEventListener(
  73. [ev[0]],
  74. function(e) {
  75. if (_this[ev[1]]) _this[ev[1]](e);
  76. },
  77. false
  78. );
  79. });
  80. }
  81. Gesture.prototype = {
  82. pos: function(e, identifier) {
  83. var target = this.target(e, identifier);
  84. if (target) {
  85. return {
  86. x: target.clientX,
  87. y: target.clientY,
  88. identifier: identifier
  89. };
  90. } else {
  91. return false;
  92. }
  93. },
  94. target: function(e, identifier) {
  95. var target;
  96. if (SUPPORT_TOUCH) {
  97. if (typeof identifier === "number") {
  98. e.changedTouches.forEach(function(touch) {
  99. if (touch.identifier === identifier) target = touch;
  100. });
  101. } else {
  102. target = e.changedTouches[0];
  103. }
  104. } else {
  105. target = e;
  106. }
  107. return target;
  108. },
  109. dispatch: function(e, name, detail) {
  110. e.target.dispatchEvent(
  111. new CustomEvent(name, {
  112. detail: detail,
  113. bubbles: true,
  114. cancelable: true
  115. })
  116. );
  117. }
  118. };
  119. var tap = new Gesture({
  120. name: "tap",
  121. init: function() {
  122. this.moved = false;
  123. this.startX = 0;
  124. this.startY = 0;
  125. this.number = 0;
  126. this.identifier = undefined;
  127. },
  128. start: function(e) {
  129. if (SUPPORT_TOUCH && e.touches.length > 1) return;
  130. this.init();
  131. var pos = this.pos(e);
  132. this.startX = pos.x;
  133. this.startY = pos.y;
  134. this.identifier = pos.identifier;
  135. },
  136. move: function(e) {
  137. var pos = this.pos(e, this.identifier);
  138. if (
  139. pos &&
  140. (Math.abs(pos.x - this.startX) > 10 || Math.abs(pos.y - this.startY) > 10)
  141. ) {
  142. this.moved = true;
  143. }
  144. },
  145. end: function(e) {
  146. if (this.target(e, this.identifier) && !this.moved) {
  147. this.dispatch(e, this.name, this.pos(e));
  148. }
  149. },
  150. cancel: function(e) {
  151. this.end(e)
  152. }
  153. });
  154. var pan = new Gesture({
  155. name: "pan",
  156. init: function() {
  157. this.isStart = false;
  158. this.startX = 0;
  159. this.startY = 0;
  160. this.prevX = 0;
  161. this.prevY = 0;
  162. this.prevT = 0;
  163. this.identifier = undefined;
  164. this.history = [];
  165. this.directionX = "none";
  166. this.directionY = "none";
  167. },
  168. start: function(e) {
  169. if (SUPPORT_TOUCH && e.touches.length > 1) return;
  170. this.init();
  171. var pos = this.pos(e);
  172. this.startX = this.prevX = pos.x;
  173. this.startY = this.prevY = pos.y;
  174. this.prevY = e.timeStamp;
  175. this.identifier = pos.identifier;
  176. },
  177. move: function(e) {
  178. var pos = this.pos(e, this.identifier);
  179. if (pos) {
  180. var detail = this.detail(pos);
  181. // 方向相同且时间短记录,不然清空
  182. if (
  183. detail.directionX === this.directionX &&
  184. e.timeStamp - this.prevT < 50
  185. ) {
  186. if (this.history.length >= 10) this.history.shift();
  187. } else {
  188. this.history = [];
  189. }
  190. this.history.push({
  191. x: detail.x,
  192. y: detail.y,
  193. t: e.timeStamp,
  194. deltaX: detail.deltaX,
  195. deltaY: detail.deltaY
  196. });
  197. if (!this.isStart) {
  198. this.isStart = true;
  199. this.dispatch(e, this.name + "start", detail);
  200. }
  201. this.throttleDispatch(e, this.name, detail)
  202. this.prevX = pos.x;
  203. this.prevY = pos.y;
  204. this.prevT = e.timeStamp;
  205. this.directionX = detail.directionX;
  206. this.directionY = detail.directionY;
  207. }
  208. },
  209. end: function(e) {
  210. var pos = this.pos(e, this.identifier);
  211. var detail = this.detail(pos);
  212. detail.fast =
  213. this.history.length > 3 &&
  214. e.timeStamp - this.prevT < 50 &&
  215. Math.abs(
  216. (this.history[this.history.length - 1].x - this.history[0].x) /
  217. (this.history[this.history.length - 1].t - this.history[0].t)
  218. ) > 1;
  219. if (pos && this.isStart) this.dispatch(e, this.name + "end", detail);
  220. },
  221. cancel: function(e) {
  222. this.end(e);
  223. },
  224. detail: function(pos) {
  225. var deltaX = pos.x - this.startX,
  226. deltaY = pos.y - this.startY;
  227. if (this.prevX < pos.x) {
  228. var directionX = "right";
  229. } else if (this.prevX > pos.x) {
  230. var directionX = "left";
  231. } else {
  232. directionX = this.directionX;
  233. }
  234. if (this.prevY < pos.y) {
  235. var directionY = "bottom";
  236. } else if (this.prevY > pos.y) {
  237. var directionY = "top";
  238. } else {
  239. directionY = this.directionY;
  240. }
  241. return {
  242. x: pos.x,
  243. y: pos.y,
  244. deltaX: deltaX,
  245. deltaY: deltaY,
  246. directionY: directionY,
  247. directionX: directionX
  248. };
  249. },
  250. throttleDispatch: throttle(Gesture.prototype.dispatch,20,{ trailing: false })
  251. });