mui.gestures.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. /**
  2. * mui gestures
  3. * @param {type} $
  4. * @param {type} window
  5. * @returns {undefined}
  6. */
  7. (function($, window) {
  8. $.gestures = {
  9. session: {}
  10. };
  11. /**
  12. * Gesture preventDefault
  13. * @param {type} e
  14. * @returns {undefined}
  15. */
  16. $.preventDefault = function(e) {
  17. e.preventDefault();
  18. };
  19. /**
  20. * Gesture stopPropagation
  21. * @param {type} e
  22. * @returns {undefined}
  23. */
  24. $.stopPropagation = function(e) {
  25. e.stopPropagation();
  26. };
  27. /**
  28. * register gesture
  29. * @param {type} gesture
  30. * @returns {$.gestures}
  31. */
  32. $.addGesture = function(gesture) {
  33. return $.addAction('gestures', gesture);
  34. };
  35. var round = Math.round;
  36. var abs = Math.abs;
  37. var sqrt = Math.sqrt;
  38. var atan = Math.atan;
  39. var atan2 = Math.atan2;
  40. /**
  41. * distance
  42. * @param {type} p1
  43. * @param {type} p2
  44. * @returns {Number}
  45. */
  46. var getDistance = function(p1, p2, props) {
  47. if(!props) {
  48. props = ['x', 'y'];
  49. }
  50. var x = p2[props[0]] - p1[props[0]];
  51. var y = p2[props[1]] - p1[props[1]];
  52. return sqrt((x * x) + (y * y));
  53. };
  54. /**
  55. * scale
  56. * @param {Object} starts
  57. * @param {Object} moves
  58. */
  59. var getScale = function(starts, moves) {
  60. if(starts.length >= 2 && moves.length >= 2) {
  61. var props = ['pageX', 'pageY'];
  62. return getDistance(moves[1], moves[0], props) / getDistance(starts[1], starts[0], props);
  63. }
  64. return 1;
  65. };
  66. /**
  67. * angle
  68. * @param {type} p1
  69. * @param {type} p2
  70. * @returns {Number}
  71. */
  72. var getAngle = function(p1, p2, props) {
  73. if(!props) {
  74. props = ['x', 'y'];
  75. }
  76. var x = p2[props[0]] - p1[props[0]];
  77. var y = p2[props[1]] - p1[props[1]];
  78. return atan2(y, x) * 180 / Math.PI;
  79. };
  80. /**
  81. * direction
  82. * @param {Object} x
  83. * @param {Object} y
  84. */
  85. var getDirection = function(x, y) {
  86. if(x === y) {
  87. return '';
  88. }
  89. if(abs(x) >= abs(y)) {
  90. return x > 0 ? 'left' : 'right';
  91. }
  92. return y > 0 ? 'up' : 'down';
  93. };
  94. /**
  95. * rotation
  96. * @param {Object} start
  97. * @param {Object} end
  98. */
  99. var getRotation = function(start, end) {
  100. var props = ['pageX', 'pageY'];
  101. return getAngle(end[1], end[0], props) - getAngle(start[1], start[0], props);
  102. };
  103. /**
  104. * px per ms
  105. * @param {Object} deltaTime
  106. * @param {Object} x
  107. * @param {Object} y
  108. */
  109. var getVelocity = function(deltaTime, x, y) {
  110. return {
  111. x: x / deltaTime || 0,
  112. y: y / deltaTime || 0
  113. };
  114. };
  115. /**
  116. * detect gestures
  117. * @param {type} event
  118. * @param {type} touch
  119. * @returns {undefined}
  120. */
  121. var detect = function(event, touch) {
  122. if($.gestures.stoped) {
  123. return;
  124. }
  125. $.doAction('gestures', function(index, gesture) {
  126. if(!$.gestures.stoped) {
  127. if($.options.gestureConfig[gesture.name] !== false) {
  128. gesture.handle(event, touch);
  129. }
  130. }
  131. });
  132. };
  133. /**
  134. * 暂时无用
  135. * @param {Object} node
  136. * @param {Object} parent
  137. */
  138. var hasParent = function(node, parent) {
  139. while(node) {
  140. if(node == parent) {
  141. return true;
  142. }
  143. node = node.parentNode;
  144. }
  145. return false;
  146. };
  147. var uniqueArray = function(src, key, sort) {
  148. var results = [];
  149. var values = [];
  150. var i = 0;
  151. while(i < src.length) {
  152. var val = key ? src[i][key] : src[i];
  153. if(values.indexOf(val) < 0) {
  154. results.push(src[i]);
  155. }
  156. values[i] = val;
  157. i++;
  158. }
  159. if(sort) {
  160. if(!key) {
  161. results = results.sort();
  162. } else {
  163. results = results.sort(function sortUniqueArray(a, b) {
  164. return a[key] > b[key];
  165. });
  166. }
  167. }
  168. return results;
  169. };
  170. var getMultiCenter = function(touches) {
  171. var touchesLength = touches.length;
  172. if(touchesLength === 1) {
  173. return {
  174. x: round(touches[0].pageX),
  175. y: round(touches[0].pageY)
  176. };
  177. }
  178. var x = 0;
  179. var y = 0;
  180. var i = 0;
  181. while(i < touchesLength) {
  182. x += touches[i].pageX;
  183. y += touches[i].pageY;
  184. i++;
  185. }
  186. return {
  187. x: round(x / touchesLength),
  188. y: round(y / touchesLength)
  189. };
  190. };
  191. var multiTouch = function() {
  192. return $.options.gestureConfig.pinch;
  193. };
  194. var copySimpleTouchData = function(touch) {
  195. var touches = [];
  196. var i = 0;
  197. while(i < touch.touches.length) {
  198. touches[i] = {
  199. pageX: round(touch.touches[i].pageX),
  200. pageY: round(touch.touches[i].pageY)
  201. };
  202. i++;
  203. }
  204. return {
  205. timestamp: $.now(),
  206. gesture: touch.gesture,
  207. touches: touches,
  208. center: getMultiCenter(touch.touches),
  209. deltaX: touch.deltaX,
  210. deltaY: touch.deltaY
  211. };
  212. };
  213. var calDelta = function(touch) {
  214. var session = $.gestures.session;
  215. var center = touch.center;
  216. var offset = session.offsetDelta || {};
  217. var prevDelta = session.prevDelta || {};
  218. var prevTouch = session.prevTouch || {};
  219. if(touch.gesture.type === $.EVENT_START || touch.gesture.type === $.EVENT_END) {
  220. prevDelta = session.prevDelta = {
  221. x: prevTouch.deltaX || 0,
  222. y: prevTouch.deltaY || 0
  223. };
  224. offset = session.offsetDelta = {
  225. x: center.x,
  226. y: center.y
  227. };
  228. }
  229. touch.deltaX = prevDelta.x + (center.x - offset.x);
  230. touch.deltaY = prevDelta.y + (center.y - offset.y);
  231. };
  232. var calTouchData = function(touch) {
  233. var session = $.gestures.session;
  234. var touches = touch.touches;
  235. var touchesLength = touches.length;
  236. if(!session.firstTouch) {
  237. session.firstTouch = copySimpleTouchData(touch);
  238. }
  239. if(multiTouch() && touchesLength > 1 && !session.firstMultiTouch) {
  240. session.firstMultiTouch = copySimpleTouchData(touch);
  241. } else if(touchesLength === 1) {
  242. session.firstMultiTouch = false;
  243. }
  244. var firstTouch = session.firstTouch;
  245. var firstMultiTouch = session.firstMultiTouch;
  246. var offsetCenter = firstMultiTouch ? firstMultiTouch.center : firstTouch.center;
  247. var center = touch.center = getMultiCenter(touches);
  248. touch.timestamp = $.now();
  249. touch.deltaTime = touch.timestamp - firstTouch.timestamp;
  250. touch.angle = getAngle(offsetCenter, center);
  251. touch.distance = getDistance(offsetCenter, center);
  252. calDelta(touch);
  253. touch.offsetDirection = getDirection(touch.deltaX, touch.deltaY);
  254. touch.scale = firstMultiTouch ? getScale(firstMultiTouch.touches, touches) : 1;
  255. touch.rotation = firstMultiTouch ? getRotation(firstMultiTouch.touches, touches) : 0;
  256. calIntervalTouchData(touch);
  257. };
  258. var CAL_INTERVAL = 25;
  259. var calIntervalTouchData = function(touch) {
  260. var session = $.gestures.session;
  261. var last = session.lastInterval || touch;
  262. var deltaTime = touch.timestamp - last.timestamp;
  263. var velocity;
  264. var velocityX;
  265. var velocityY;
  266. var direction;
  267. if(touch.gesture.type != $.EVENT_CANCEL && (deltaTime > CAL_INTERVAL || last.velocity === undefined)) {
  268. var deltaX = last.deltaX - touch.deltaX;
  269. var deltaY = last.deltaY - touch.deltaY;
  270. var v = getVelocity(deltaTime, deltaX, deltaY);
  271. velocityX = v.x;
  272. velocityY = v.y;
  273. velocity = (abs(v.x) > abs(v.y)) ? v.x : v.y;
  274. direction = getDirection(deltaX, deltaY) || last.direction;
  275. session.lastInterval = touch;
  276. } else {
  277. velocity = last.velocity;
  278. velocityX = last.velocityX;
  279. velocityY = last.velocityY;
  280. direction = last.direction;
  281. }
  282. touch.velocity = velocity;
  283. touch.velocityX = velocityX;
  284. touch.velocityY = velocityY;
  285. touch.direction = direction;
  286. };
  287. var targetIds = {};
  288. var convertTouches = function(touches) {
  289. for(var i = 0; i < touches.length; i++) {
  290. !touches['identifier'] && (touches['identifier'] = 0);
  291. }
  292. return touches;
  293. };
  294. var getTouches = function(event, touch) {
  295. var allTouches = convertTouches($.slice.call(event.touches || [event]));
  296. var type = event.type;
  297. var targetTouches = [];
  298. var changedTargetTouches = [];
  299. //当touchstart或touchmove且touches长度为1,直接获得all和changed
  300. if((type === $.EVENT_START || type === $.EVENT_MOVE) && allTouches.length === 1) {
  301. targetIds[allTouches[0].identifier] = true;
  302. targetTouches = allTouches;
  303. changedTargetTouches = allTouches;
  304. touch.target = event.target;
  305. } else {
  306. var i = 0;
  307. var targetTouches = [];
  308. var changedTargetTouches = [];
  309. var changedTouches = convertTouches($.slice.call(event.changedTouches || [event]));
  310. touch.target = event.target;
  311. var sessionTarget = $.gestures.session.target || event.target;
  312. targetTouches = allTouches.filter(function(touch) {
  313. return hasParent(touch.target, sessionTarget);
  314. });
  315. if(type === $.EVENT_START) {
  316. i = 0;
  317. while(i < targetTouches.length) {
  318. targetIds[targetTouches[i].identifier] = true;
  319. i++;
  320. }
  321. }
  322. i = 0;
  323. while(i < changedTouches.length) {
  324. if(targetIds[changedTouches[i].identifier]) {
  325. changedTargetTouches.push(changedTouches[i]);
  326. }
  327. if(type === $.EVENT_END || type === $.EVENT_CANCEL) {
  328. delete targetIds[changedTouches[i].identifier];
  329. }
  330. i++;
  331. }
  332. if(!changedTargetTouches.length) {
  333. return false;
  334. }
  335. }
  336. targetTouches = uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true);
  337. var touchesLength = targetTouches.length;
  338. var changedTouchesLength = changedTargetTouches.length;
  339. if(type === $.EVENT_START && touchesLength - changedTouchesLength === 0) { //first
  340. touch.isFirst = true;
  341. $.gestures.touch = $.gestures.session = {
  342. target: event.target
  343. };
  344. }
  345. touch.isFinal = ((type === $.EVENT_END || type === $.EVENT_CANCEL) && (touchesLength - changedTouchesLength === 0));
  346. touch.touches = targetTouches;
  347. touch.changedTouches = changedTargetTouches;
  348. return true;
  349. };
  350. var handleTouchEvent = function(event) {
  351. var touch = {
  352. gesture: event
  353. };
  354. var touches = getTouches(event, touch);
  355. if(!touches) {
  356. return;
  357. }
  358. calTouchData(touch);
  359. detect(event, touch);
  360. $.gestures.session.prevTouch = touch;
  361. if(event.type === $.EVENT_END && !$.isTouchable) {
  362. $.gestures.touch = $.gestures.session = {};
  363. }
  364. };
  365. var supportsPassive = (function checkPassiveListener() {
  366. var supportsPassive = false;
  367. try {
  368. var opts = Object.defineProperty({}, 'passive', {
  369. get: function get() {
  370. supportsPassive = true;
  371. },
  372. });
  373. window.addEventListener('testPassiveListener', null, opts);
  374. } catch(e) {
  375. // No support
  376. }
  377. return supportsPassive;
  378. }())
  379. window.addEventListener($.EVENT_START, handleTouchEvent);
  380. window.addEventListener($.EVENT_MOVE, handleTouchEvent, supportsPassive ? {
  381. passive: false,
  382. capture: false
  383. } : false);
  384. window.addEventListener($.EVENT_END, handleTouchEvent);
  385. window.addEventListener($.EVENT_CANCEL, handleTouchEvent);
  386. //fixed hashchange(android)
  387. window.addEventListener($.EVENT_CLICK, function(e) {
  388. //TODO 应该判断当前target是不是在targets.popover内部,而不是非要相等
  389. if(($.os.android || $.os.ios) && (($.targets.popover && e.target === $.targets.popover) || ($.targets.tab) || $.targets.offcanvas || $.targets.modal)) {
  390. e.preventDefault();
  391. }
  392. }, true);
  393. //增加原生滚动识别
  394. $.isScrolling = false;
  395. var scrollingTimeout = null;
  396. window.addEventListener('scroll', function() {
  397. $.isScrolling = true;
  398. scrollingTimeout && clearTimeout(scrollingTimeout);
  399. scrollingTimeout = setTimeout(function() {
  400. $.isScrolling = false;
  401. }, 250);
  402. });
  403. })(mui, window);