You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1101 lines
33 KiB

  1. /*!
  2. * iScroll v4.2 ~ Copyright (c) 2012 Matteo Spinelli, http://cubiq.org
  3. * Released under MIT license, http://cubiq.org/license
  4. */
  5. (function(window, doc){
  6. var m = Math,
  7. dummyStyle = doc.createElement('div').style,
  8. vendor = (function () {
  9. var vendors = 't,webkitT,MozT,msT,OT'.split(','),
  10. t,
  11. i = 0,
  12. l = vendors.length;
  13. for ( ; i < l; i++ ) {
  14. t = vendors[i] + 'ransform';
  15. if ( t in dummyStyle ) {
  16. return vendors[i].substr(0, vendors[i].length - 1);
  17. }
  18. }
  19. return false;
  20. })(),
  21. cssVendor = vendor ? '-' + vendor.toLowerCase() + '-' : '',
  22. // Style properties
  23. transform = prefixStyle('transform'),
  24. transitionProperty = prefixStyle('transitionProperty'),
  25. transitionDuration = prefixStyle('transitionDuration'),
  26. transformOrigin = prefixStyle('transformOrigin'),
  27. transitionTimingFunction = prefixStyle('transitionTimingFunction'),
  28. transitionDelay = prefixStyle('transitionDelay'),
  29. // Browser capabilities
  30. isAndroid = (/android/gi).test(navigator.appVersion),
  31. isIDevice = (/iphone|ipad/gi).test(navigator.appVersion),
  32. isTouchPad = (/hp-tablet/gi).test(navigator.appVersion),
  33. has3d = prefixStyle('perspective') in dummyStyle,
  34. hasTouch = 'ontouchstart' in window && !isTouchPad,
  35. hasTransform = !!vendor,
  36. hasTransitionEnd = prefixStyle('transition') in dummyStyle,
  37. RESIZE_EV = 'onorientationchange' in window ? 'orientationchange' : 'resize',
  38. START_EV = hasTouch ? 'touchstart' : 'mousedown',
  39. MOVE_EV = hasTouch ? 'touchmove' : 'mousemove',
  40. END_EV = hasTouch ? 'touchend' : 'mouseup',
  41. CANCEL_EV = hasTouch ? 'touchcancel' : 'mouseup',
  42. WHEEL_EV = vendor == 'Moz' ? 'DOMMouseScroll' : 'mousewheel',
  43. TRNEND_EV = (function () {
  44. if ( vendor === false ) return false;
  45. var transitionEnd = {
  46. '' : 'transitionend',
  47. 'webkit' : 'webkitTransitionEnd',
  48. 'Moz' : 'transitionend',
  49. 'O' : 'oTransitionEnd',
  50. 'ms' : 'MSTransitionEnd'
  51. };
  52. return transitionEnd[vendor];
  53. })(),
  54. nextFrame = (function() {
  55. return window.requestAnimationFrame ||
  56. window.webkitRequestAnimationFrame ||
  57. window.mozRequestAnimationFrame ||
  58. window.oRequestAnimationFrame ||
  59. window.msRequestAnimationFrame ||
  60. function(callback) { return setTimeout(callback, 1); };
  61. })(),
  62. cancelFrame = (function () {
  63. return window.cancelRequestAnimationFrame ||
  64. window.webkitCancelAnimationFrame ||
  65. window.webkitCancelRequestAnimationFrame ||
  66. window.mozCancelRequestAnimationFrame ||
  67. window.oCancelRequestAnimationFrame ||
  68. window.msCancelRequestAnimationFrame ||
  69. clearTimeout;
  70. })(),
  71. // Helpers
  72. translateZ = has3d ? ' translateZ(0)' : '',
  73. // Constructor
  74. iScroll = function (el, options) {
  75. var that = this,
  76. i;
  77. that.wrapper = typeof el == 'object' ? el : doc.getElementById(el);
  78. that.wrapper.style.overflow = 'hidden';
  79. that.scroller = that.wrapper.children[0];
  80. // Default options
  81. that.options = {
  82. hScroll: true,
  83. vScroll: true,
  84. x: 0,
  85. y: 0,
  86. bounce: true,
  87. bounceLock: false,
  88. momentum: true,
  89. lockDirection: true,
  90. useTransform: true,
  91. useTransition: false,
  92. topOffset: 0,
  93. checkDOMChanges: false, // Experimental
  94. handleClick: true,
  95. // Scrollbar
  96. hScrollbar: true,
  97. vScrollbar: true,
  98. fixedScrollbar: isAndroid,
  99. hideScrollbar: isIDevice,
  100. fadeScrollbar: isIDevice && has3d,
  101. scrollbarClass: '',
  102. // Zoom
  103. zoom: false,
  104. zoomMin: 1,
  105. zoomMax: 4,
  106. doubleTapZoom: 2,
  107. wheelAction: 'scroll',
  108. // Snap
  109. snap: false,
  110. snapThreshold: 1,
  111. // Events
  112. onRefresh: null,
  113. onBeforeScrollStart: function (e) { e.preventDefault(); },
  114. onScrollStart: null,
  115. onBeforeScrollMove: null,
  116. onScrollMove: null,
  117. onBeforeScrollEnd: null,
  118. onScrollEnd: null,
  119. onTouchEnd: null,
  120. onDestroy: null,
  121. onZoomStart: null,
  122. onZoom: null,
  123. onZoomEnd: null
  124. };
  125. // User defined options
  126. for (i in options) that.options[i] = options[i];
  127. // Set starting position
  128. that.x = that.options.x;
  129. that.y = that.options.y;
  130. // Normalize options
  131. that.options.useTransform = hasTransform && that.options.useTransform;
  132. that.options.hScrollbar = that.options.hScroll && that.options.hScrollbar;
  133. that.options.vScrollbar = that.options.vScroll && that.options.vScrollbar;
  134. that.options.zoom = that.options.useTransform && that.options.zoom;
  135. that.options.useTransition = hasTransitionEnd && that.options.useTransition;
  136. // Helpers FIX ANDROID BUG!
  137. // translate3d and scale doesn't work together!
  138. // Ignoring 3d ONLY WHEN YOU SET that.options.zoom
  139. if ( that.options.zoom && isAndroid ){
  140. translateZ = '';
  141. }
  142. // Set some default styles
  143. that.scroller.style[transitionProperty] = that.options.useTransform ? cssVendor + 'transform' : 'top left';
  144. that.scroller.style[transitionDuration] = '0';
  145. that.scroller.style[transformOrigin] = '0 0';
  146. if (that.options.useTransition) that.scroller.style[transitionTimingFunction] = 'cubic-bezier(0.33,0.66,0.66,1)';
  147. if (that.options.useTransform) that.scroller.style[transform] = 'translate(' + that.x + 'px,' + that.y + 'px)' + translateZ;
  148. else that.scroller.style.cssText += ';position:absolute;top:' + that.y + 'px;left:' + that.x + 'px';
  149. if (that.options.useTransition) that.options.fixedScrollbar = true;
  150. that.refresh();
  151. that._bind(RESIZE_EV, window);
  152. that._bind(START_EV);
  153. if (!hasTouch) {
  154. if (that.options.wheelAction != 'none')
  155. that._bind(WHEEL_EV);
  156. }
  157. if (that.options.checkDOMChanges) that.checkDOMTime = setInterval(function () {
  158. that._checkDOMChanges();
  159. }, 500);
  160. };
  161. // Prototype
  162. iScroll.prototype = {
  163. enabled: true,
  164. x: 0,
  165. y: 0,
  166. steps: [],
  167. scale: 1,
  168. currPageX: 0, currPageY: 0,
  169. pagesX: [], pagesY: [],
  170. aniTime: null,
  171. wheelZoomCount: 0,
  172. handleEvent: function (e) {
  173. var that = this;
  174. switch(e.type) {
  175. case START_EV:
  176. if (!hasTouch && e.button !== 0) return;
  177. that._start(e);
  178. break;
  179. case MOVE_EV: that._move(e); break;
  180. case END_EV:
  181. case CANCEL_EV: that._end(e); break;
  182. case RESIZE_EV: that._resize(); break;
  183. case WHEEL_EV: that._wheel(e); break;
  184. case TRNEND_EV: that._transitionEnd(e); break;
  185. }
  186. },
  187. _checkDOMChanges: function () {
  188. if (this.moved || this.zoomed || this.animating ||
  189. (this.scrollerW == this.scroller.offsetWidth * this.scale && this.scrollerH == this.scroller.offsetHeight * this.scale)) return;
  190. this.refresh();
  191. },
  192. _scrollbar: function (dir) {
  193. var that = this,
  194. bar;
  195. if (!that[dir + 'Scrollbar']) {
  196. if (that[dir + 'ScrollbarWrapper']) {
  197. if (hasTransform) that[dir + 'ScrollbarIndicator'].style[transform] = '';
  198. that[dir + 'ScrollbarWrapper'].parentNode.removeChild(that[dir + 'ScrollbarWrapper']);
  199. that[dir + 'ScrollbarWrapper'] = null;
  200. that[dir + 'ScrollbarIndicator'] = null;
  201. }
  202. return;
  203. }
  204. if (!that[dir + 'ScrollbarWrapper']) {
  205. // Create the scrollbar wrapper
  206. bar = doc.createElement('div');
  207. if (that.options.scrollbarClass) bar.className = that.options.scrollbarClass + dir.toUpperCase();
  208. else bar.style.cssText = 'position:absolute;z-index:100;' + (dir == 'h' ? 'height:7px;bottom:1px;left:2px;right:' + (that.vScrollbar ? '7' : '2') + 'px' : 'width:7px;bottom:' + (that.hScrollbar ? '7' : '2') + 'px;top:2px;right:1px');
  209. bar.style.cssText += ';pointer-events:none;' + cssVendor + 'transition-property:opacity;' + cssVendor + 'transition-duration:' + (that.options.fadeScrollbar ? '350ms' : '0') + ';overflow:hidden;opacity:' + (that.options.hideScrollbar ? '0' : '1');
  210. that.wrapper.appendChild(bar);
  211. that[dir + 'ScrollbarWrapper'] = bar;
  212. // Create the scrollbar indicator
  213. bar = doc.createElement('div');
  214. if (!that.options.scrollbarClass) {
  215. bar.style.cssText = 'position:absolute;z-index:100;background:rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.9);' + cssVendor + 'background-clip:padding-box;' + cssVendor + 'box-sizing:border-box;' + (dir == 'h' ? 'height:100%' : 'width:100%') + ';' + cssVendor + 'border-radius:3px;border-radius:3px';
  216. }
  217. bar.style.cssText += ';pointer-events:none;' + cssVendor + 'transition-property:' + cssVendor + 'transform;' + cssVendor + 'transition-timing-function:cubic-bezier(0.33,0.66,0.66,1);' + cssVendor + 'transition-duration:0;' + cssVendor + 'transform: translate(0,0)' + translateZ;
  218. if (that.options.useTransition) bar.style.cssText += ';' + cssVendor + 'transition-timing-function:cubic-bezier(0.33,0.66,0.66,1)';
  219. that[dir + 'ScrollbarWrapper'].appendChild(bar);
  220. that[dir + 'ScrollbarIndicator'] = bar;
  221. }
  222. if (dir == 'h') {
  223. that.hScrollbarSize = that.hScrollbarWrapper.clientWidth;
  224. that.hScrollbarIndicatorSize = m.max(m.round(that.hScrollbarSize * that.hScrollbarSize / that.scrollerW), 8);
  225. that.hScrollbarIndicator.style.width = that.hScrollbarIndicatorSize + 'px';
  226. that.hScrollbarMaxScroll = that.hScrollbarSize - that.hScrollbarIndicatorSize;
  227. that.hScrollbarProp = that.hScrollbarMaxScroll / that.maxScrollX;
  228. } else {
  229. that.vScrollbarSize = that.vScrollbarWrapper.clientHeight;
  230. that.vScrollbarIndicatorSize = m.max(m.round(that.vScrollbarSize * that.vScrollbarSize / that.scrollerH), 8);
  231. that.vScrollbarIndicator.style.height = that.vScrollbarIndicatorSize + 'px';
  232. that.vScrollbarMaxScroll = that.vScrollbarSize - that.vScrollbarIndicatorSize;
  233. that.vScrollbarProp = that.vScrollbarMaxScroll / that.maxScrollY;
  234. }
  235. // Reset position
  236. that._scrollbarPos(dir, true);
  237. },
  238. _resize: function () {
  239. var that = this;
  240. setTimeout(function () { that.refresh(); }, isAndroid ? 200 : 0);
  241. },
  242. _pos: function (x, y) {
  243. if (this.zoomed) return;
  244. x = this.hScroll ? x : 0;
  245. y = this.vScroll ? y : 0;
  246. if (this.options.useTransform) {
  247. this.scroller.style[transform] = 'translate(' + x + 'px,' + y + 'px) scale(' + this.scale + ')' + translateZ;
  248. } else {
  249. x = m.round(x);
  250. y = m.round(y);
  251. this.scroller.style.left = x + 'px';
  252. this.scroller.style.top = y + 'px';
  253. }
  254. this.x = x;
  255. this.y = y;
  256. this._scrollbarPos('h');
  257. this._scrollbarPos('v');
  258. },
  259. _scrollbarPos: function (dir, hidden) {
  260. var that = this,
  261. pos = dir == 'h' ? that.x : that.y,
  262. size;
  263. if (!that[dir + 'Scrollbar']) return;
  264. pos = that[dir + 'ScrollbarProp'] * pos;
  265. if (pos < 0) {
  266. if (!that.options.fixedScrollbar) {
  267. size = that[dir + 'ScrollbarIndicatorSize'] + m.round(pos * 3);
  268. if (size < 8) size = 8;
  269. that[dir + 'ScrollbarIndicator'].style[dir == 'h' ? 'width' : 'height'] = size + 'px';
  270. }
  271. pos = 0;
  272. } else if (pos > that[dir + 'ScrollbarMaxScroll']) {
  273. if (!that.options.fixedScrollbar) {
  274. size = that[dir + 'ScrollbarIndicatorSize'] - m.round((pos - that[dir + 'ScrollbarMaxScroll']) * 3);
  275. if (size < 8) size = 8;
  276. that[dir + 'ScrollbarIndicator'].style[dir == 'h' ? 'width' : 'height'] = size + 'px';
  277. pos = that[dir + 'ScrollbarMaxScroll'] + (that[dir + 'ScrollbarIndicatorSize'] - size);
  278. } else {
  279. pos = that[dir + 'ScrollbarMaxScroll'];
  280. }
  281. }
  282. that[dir + 'ScrollbarWrapper'].style[transitionDelay] = '0';
  283. that[dir + 'ScrollbarWrapper'].style.opacity = hidden && that.options.hideScrollbar ? '0' : '1';
  284. that[dir + 'ScrollbarIndicator'].style[transform] = 'translate(' + (dir == 'h' ? pos + 'px,0)' : '0,' + pos + 'px)') + translateZ;
  285. },
  286. _start: function (e) {
  287. var that = this,
  288. point = hasTouch ? e.touches[0] : e,
  289. matrix, x, y,
  290. c1, c2;
  291. if (!that.enabled) return;
  292. if (that.options.onBeforeScrollStart) that.options.onBeforeScrollStart.call(that, e);
  293. if (that.options.useTransition || that.options.zoom) that._transitionTime(0);
  294. that.moved = false;
  295. that.animating = false;
  296. that.zoomed = false;
  297. that.distX = 0;
  298. that.distY = 0;
  299. that.absDistX = 0;
  300. that.absDistY = 0;
  301. that.dirX = 0;
  302. that.dirY = 0;
  303. // Gesture start
  304. if (that.options.zoom && hasTouch && e.touches.length > 1) {
  305. c1 = m.abs(e.touches[0].pageX-e.touches[1].pageX);
  306. c2 = m.abs(e.touches[0].pageY-e.touches[1].pageY);
  307. that.touchesDistStart = m.sqrt(c1 * c1 + c2 * c2);
  308. that.originX = m.abs(e.touches[0].pageX + e.touches[1].pageX - that.wrapperOffsetLeft * 2) / 2 - that.x;
  309. that.originY = m.abs(e.touches[0].pageY + e.touches[1].pageY - that.wrapperOffsetTop * 2) / 2 - that.y;
  310. if (that.options.onZoomStart) that.options.onZoomStart.call(that, e);
  311. }
  312. if (that.options.momentum) {
  313. if (that.options.useTransform) {
  314. // Very lame general purpose alternative to CSSMatrix
  315. matrix = getComputedStyle(that.scroller, null)[transform].replace(/[^0-9\-.,]/g, '').split(',');
  316. x = matrix[4] * 1;
  317. y = matrix[5] * 1;
  318. } else {
  319. x = getComputedStyle(that.scroller, null).left.replace(/[^0-9-]/g, '') * 1;
  320. y = getComputedStyle(that.scroller, null).top.replace(/[^0-9-]/g, '') * 1;
  321. }
  322. if (x != that.x || y != that.y) {
  323. if (that.options.useTransition) that._unbind(TRNEND_EV);
  324. else cancelFrame(that.aniTime);
  325. that.steps = [];
  326. that._pos(x, y);
  327. }
  328. }
  329. that.absStartX = that.x; // Needed by snap threshold
  330. that.absStartY = that.y;
  331. that.startX = that.x;
  332. that.startY = that.y;
  333. that.pointX = point.pageX;
  334. that.pointY = point.pageY;
  335. that.startTime = e.timeStamp || Date.now();
  336. if (that.options.onScrollStart) that.options.onScrollStart.call(that, e);
  337. that._bind(MOVE_EV, window);
  338. that._bind(END_EV, window);
  339. that._bind(CANCEL_EV, window);
  340. },
  341. _move: function (e) {
  342. var that = this,
  343. point = hasTouch ? e.touches[0] : e,
  344. deltaX = point.pageX - that.pointX,
  345. deltaY = point.pageY - that.pointY,
  346. newX = that.x + deltaX,
  347. newY = that.y + deltaY,
  348. c1, c2, scale,
  349. timestamp = e.timeStamp || Date.now();
  350. if (that.options.onBeforeScrollMove) that.options.onBeforeScrollMove.call(that, e);
  351. // Zoom
  352. if (that.options.zoom && hasTouch && e.touches.length > 1) {
  353. c1 = m.abs(e.touches[0].pageX - e.touches[1].pageX);
  354. c2 = m.abs(e.touches[0].pageY - e.touches[1].pageY);
  355. that.touchesDist = m.sqrt(c1*c1+c2*c2);
  356. that.zoomed = true;
  357. scale = 1 / that.touchesDistStart * that.touchesDist * this.scale;
  358. if (scale < that.options.zoomMin) scale = 0.5 * that.options.zoomMin * Math.pow(2.0, scale / that.options.zoomMin);
  359. else if (scale > that.options.zoomMax) scale = 2.0 * that.options.zoomMax * Math.pow(0.5, that.options.zoomMax / scale);
  360. that.lastScale = scale / this.scale;
  361. newX = this.originX - this.originX * that.lastScale + this.x,
  362. newY = this.originY - this.originY * that.lastScale + this.y;
  363. this.scroller.style[transform] = 'translate(' + newX + 'px,' + newY + 'px) scale(' + scale + ')' + translateZ;
  364. if (that.options.onZoom) that.options.onZoom.call(that, e);
  365. return;
  366. }
  367. that.pointX = point.pageX;
  368. that.pointY = point.pageY;
  369. // Slow down if outside of the boundaries
  370. if (newX > 0 || newX < that.maxScrollX) {
  371. newX = that.options.bounce ? that.x + (deltaX / 2) : newX >= 0 || that.maxScrollX >= 0 ? 0 : that.maxScrollX;
  372. }
  373. if (newY > that.minScrollY || newY < that.maxScrollY) {
  374. newY = that.options.bounce ? that.y + (deltaY / 2) : newY >= that.minScrollY || that.maxScrollY >= 0 ? that.minScrollY : that.maxScrollY;
  375. }
  376. that.distX += deltaX;
  377. that.distY += deltaY;
  378. that.absDistX = m.abs(that.distX);
  379. that.absDistY = m.abs(that.distY);
  380. if (that.absDistX < 6 && that.absDistY < 6) {
  381. return;
  382. }
  383. // Lock direction
  384. if (that.options.lockDirection) {
  385. if (that.absDistX > that.absDistY + 5) {
  386. newY = that.y;
  387. deltaY = 0;
  388. } else if (that.absDistY > that.absDistX + 5) {
  389. newX = that.x;
  390. deltaX = 0;
  391. }
  392. }
  393. that.moved = true;
  394. that._pos(newX, newY);
  395. that.dirX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0;
  396. that.dirY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0;
  397. if (timestamp - that.startTime > 300) {
  398. that.startTime = timestamp;
  399. that.startX = that.x;
  400. that.startY = that.y;
  401. }
  402. if (that.options.onScrollMove) that.options.onScrollMove.call(that, e);
  403. },
  404. _end: function (e) {
  405. if (hasTouch && e.touches.length !== 0) return;
  406. var that = this,
  407. point = hasTouch ? e.changedTouches[0] : e,
  408. target, ev,
  409. momentumX = { dist:0, time:0 },
  410. momentumY = { dist:0, time:0 },
  411. duration = (e.timeStamp || Date.now()) - that.startTime,
  412. newPosX = that.x,
  413. newPosY = that.y,
  414. distX, distY,
  415. newDuration,
  416. snap,
  417. scale;
  418. that._unbind(MOVE_EV, window);
  419. that._unbind(END_EV, window);
  420. that._unbind(CANCEL_EV, window);
  421. if (that.options.onBeforeScrollEnd) that.options.onBeforeScrollEnd.call(that, e);
  422. if (that.zoomed) {
  423. scale = that.scale * that.lastScale;
  424. scale = Math.max(that.options.zoomMin, scale);
  425. scale = Math.min(that.options.zoomMax, scale);
  426. that.lastScale = scale / that.scale;
  427. that.scale = scale;
  428. that.x = that.originX - that.originX * that.lastScale + that.x;
  429. that.y = that.originY - that.originY * that.lastScale + that.y;
  430. that.scroller.style[transitionDuration] = '200ms';
  431. that.scroller.style[transform] = 'translate(' + that.x + 'px,' + that.y + 'px) scale(' + that.scale + ')' + translateZ;
  432. that.zoomed = false;
  433. that.refresh();
  434. if (that.options.onZoomEnd) that.options.onZoomEnd.call(that, e);
  435. return;
  436. }
  437. if (!that.moved) {
  438. if (hasTouch) {
  439. if (that.doubleTapTimer && that.options.zoom) {
  440. // Double tapped
  441. clearTimeout(that.doubleTapTimer);
  442. that.doubleTapTimer = null;
  443. if (that.options.onZoomStart) that.options.onZoomStart.call(that, e);
  444. that.zoom(that.pointX, that.pointY, that.scale == 1 ? that.options.doubleTapZoom : 1);
  445. if (that.options.onZoomEnd) {
  446. setTimeout(function() {
  447. that.options.onZoomEnd.call(that, e);
  448. }, 200); // 200 is default zoom duration
  449. }
  450. } else if (this.options.handleClick) {
  451. that.doubleTapTimer = setTimeout(function () {
  452. that.doubleTapTimer = null;
  453. // Find the last touched element
  454. target = point.target;
  455. while (target.nodeType != 1) target = target.parentNode;
  456. if (target.tagName != 'SELECT' && target.tagName != 'INPUT' && target.tagName != 'TEXTAREA') {
  457. ev = doc.createEvent('MouseEvents');
  458. ev.initMouseEvent('click', true, true, e.view, 1,
  459. point.screenX, point.screenY, point.clientX, point.clientY,
  460. e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
  461. 0, null);
  462. ev._fake = true;
  463. target.dispatchEvent(ev);
  464. }
  465. }, that.options.zoom ? 250 : 0);
  466. }
  467. }
  468. that._resetPos(400);
  469. if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e);
  470. return;
  471. }
  472. if (duration < 300 && that.options.momentum) {
  473. momentumX = newPosX ? that._momentum(newPosX - that.startX, duration, -that.x, that.scrollerW - that.wrapperW + that.x, that.options.bounce ? that.wrapperW : 0) : momentumX;
  474. momentumY = newPosY ? that._momentum(newPosY - that.startY, duration, -that.y, (that.maxScrollY < 0 ? that.scrollerH - that.wrapperH + that.y - that.minScrollY : 0), that.options.bounce ? that.wrapperH : 0) : momentumY;
  475. newPosX = that.x + momentumX.dist;
  476. newPosY = that.y + momentumY.dist;
  477. if ((that.x > 0 && newPosX > 0) || (that.x < that.maxScrollX && newPosX < that.maxScrollX)) momentumX = { dist:0, time:0 };
  478. if ((that.y > that.minScrollY && newPosY > that.minScrollY) || (that.y < that.maxScrollY && newPosY < that.maxScrollY)) momentumY = { dist:0, time:0 };
  479. }
  480. if (momentumX.dist || momentumY.dist) {
  481. newDuration = m.max(m.max(momentumX.time, momentumY.time), 10);
  482. // Do we need to snap?
  483. if (that.options.snap) {
  484. distX = newPosX - that.absStartX;
  485. distY = newPosY - that.absStartY;
  486. if (m.abs(distX) < that.options.snapThreshold && m.abs(distY) < that.options.snapThreshold) { that.scrollTo(that.absStartX, that.absStartY, 200); }
  487. else {
  488. snap = that._snap(newPosX, newPosY);
  489. newPosX = snap.x;
  490. newPosY = snap.y;
  491. newDuration = m.max(snap.time, newDuration);
  492. }
  493. }
  494. that.scrollTo(m.round(newPosX), m.round(newPosY), newDuration);
  495. if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e);
  496. return;
  497. }
  498. // Do we need to snap?
  499. if (that.options.snap) {
  500. distX = newPosX - that.absStartX;
  501. distY = newPosY - that.absStartY;
  502. if (m.abs(distX) < that.options.snapThreshold && m.abs(distY) < that.options.snapThreshold) that.scrollTo(that.absStartX, that.absStartY, 200);
  503. else {
  504. snap = that._snap(that.x, that.y);
  505. if (snap.x != that.x || snap.y != that.y) that.scrollTo(snap.x, snap.y, snap.time);
  506. }
  507. if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e);
  508. return;
  509. }
  510. that._resetPos(200);
  511. if (that.options.onTouchEnd) that.options.onTouchEnd.call(that, e);
  512. },
  513. _resetPos: function (time) {
  514. var that = this,
  515. resetX = that.x >= 0 ? 0 : that.x < that.maxScrollX ? that.maxScrollX : that.x,
  516. resetY = that.y >= that.minScrollY || that.maxScrollY > 0 ? that.minScrollY : that.y < that.maxScrollY ? that.maxScrollY : that.y;
  517. if (resetX == that.x && resetY == that.y) {
  518. if (that.moved) {
  519. that.moved = false;
  520. if (that.options.onScrollEnd) that.options.onScrollEnd.call(that); // Execute custom code on scroll end
  521. }
  522. if (that.hScrollbar && that.options.hideScrollbar) {
  523. if (vendor == 'webkit') that.hScrollbarWrapper.style[transitionDelay] = '300ms';
  524. that.hScrollbarWrapper.style.opacity = '0';
  525. }
  526. if (that.vScrollbar && that.options.hideScrollbar) {
  527. if (vendor == 'webkit') that.vScrollbarWrapper.style[transitionDelay] = '300ms';
  528. that.vScrollbarWrapper.style.opacity = '0';
  529. }
  530. return;
  531. }
  532. that.scrollTo(resetX, resetY, time || 0);
  533. },
  534. _wheel: function (e) {
  535. var that = this,
  536. wheelDeltaX, wheelDeltaY,
  537. deltaX, deltaY,
  538. deltaScale;
  539. if ('wheelDeltaX' in e) {
  540. wheelDeltaX = e.wheelDeltaX / 12;
  541. wheelDeltaY = e.wheelDeltaY / 12;
  542. } else if('wheelDelta' in e) {
  543. wheelDeltaX = wheelDeltaY = e.wheelDelta / 12;
  544. } else if ('detail' in e) {
  545. wheelDeltaX = wheelDeltaY = -e.detail * 3;
  546. } else {
  547. return;
  548. }
  549. if (that.options.wheelAction == 'zoom') {
  550. deltaScale = that.scale * Math.pow(2, 1/3 * (wheelDeltaY ? wheelDeltaY / Math.abs(wheelDeltaY) : 0));
  551. if (deltaScale < that.options.zoomMin) deltaScale = that.options.zoomMin;
  552. if (deltaScale > that.options.zoomMax) deltaScale = that.options.zoomMax;
  553. if (deltaScale != that.scale) {
  554. if (!that.wheelZoomCount && that.options.onZoomStart) that.options.onZoomStart.call(that, e);
  555. that.wheelZoomCount++;
  556. that.zoom(e.pageX, e.pageY, deltaScale, 400);
  557. setTimeout(function() {
  558. that.wheelZoomCount--;
  559. if (!that.wheelZoomCount && that.options.onZoomEnd) that.options.onZoomEnd.call(that, e);
  560. }, 400);
  561. }
  562. return;
  563. }
  564. deltaX = that.x + wheelDeltaX;
  565. deltaY = that.y + wheelDeltaY;
  566. if (deltaX > 0) deltaX = 0;
  567. else if (deltaX < that.maxScrollX) deltaX = that.maxScrollX;
  568. if (deltaY > that.minScrollY) deltaY = that.minScrollY;
  569. else if (deltaY < that.maxScrollY) deltaY = that.maxScrollY;
  570. if (that.maxScrollY < 0) {
  571. that.scrollTo(deltaX, deltaY, 0);
  572. }
  573. },
  574. _transitionEnd: function (e) {
  575. var that = this;
  576. if (e.target != that.scroller) return;
  577. that._unbind(TRNEND_EV);
  578. that._startAni();
  579. },
  580. /**
  581. *
  582. * Utilities
  583. *
  584. */
  585. _startAni: function () {
  586. var that = this,
  587. startX = that.x, startY = that.y,
  588. startTime = Date.now(),
  589. step, easeOut,
  590. animate;
  591. if (that.animating) return;
  592. if (!that.steps.length) {
  593. that._resetPos(400);
  594. return;
  595. }
  596. step = that.steps.shift();
  597. if (step.x == startX && step.y == startY) step.time = 0;
  598. that.animating = true;
  599. that.moved = true;
  600. if (that.options.useTransition) {
  601. that._transitionTime(step.time);
  602. that._pos(step.x, step.y);
  603. that.animating = false;
  604. if (step.time) that._bind(TRNEND_EV);
  605. else that._resetPos(0);
  606. return;
  607. }
  608. animate = function () {
  609. var now = Date.now(),
  610. newX, newY;
  611. if (now >= startTime + step.time) {
  612. that._pos(step.x, step.y);
  613. that.animating = false;
  614. if (that.options.onAnimationEnd) that.options.onAnimationEnd.call(that); // Execute custom code on animation end
  615. that._startAni();
  616. return;
  617. }
  618. now = (now - startTime) / step.time - 1;
  619. easeOut = m.sqrt(1 - now * now);
  620. newX = (step.x - startX) * easeOut + startX;
  621. newY = (step.y - startY) * easeOut + startY;
  622. that._pos(newX, newY);
  623. if (that.animating) that.aniTime = nextFrame(animate);
  624. };
  625. animate();
  626. },
  627. _transitionTime: function (time) {
  628. time += 'ms';
  629. this.scroller.style[transitionDuration] = time;
  630. if (this.hScrollbar) this.hScrollbarIndicator.style[transitionDuration] = time;
  631. if (this.vScrollbar) this.vScrollbarIndicator.style[transitionDuration] = time;
  632. },
  633. _momentum: function (dist, time, maxDistUpper, maxDistLower, size) {
  634. var deceleration = 0.0006,
  635. speed = m.abs(dist) / time,
  636. newDist = (speed * speed) / (2 * deceleration),
  637. newTime = 0, outsideDist = 0;
  638. // Proportinally reduce speed if we are outside of the boundaries
  639. if (dist > 0 && newDist > maxDistUpper) {
  640. outsideDist = size / (6 / (newDist / speed * deceleration));
  641. maxDistUpper = maxDistUpper + outsideDist;
  642. speed = speed * maxDistUpper / newDist;
  643. newDist = maxDistUpper;
  644. } else if (dist < 0 && newDist > maxDistLower) {
  645. outsideDist = size / (6 / (newDist / speed * deceleration));
  646. maxDistLower = maxDistLower + outsideDist;
  647. speed = speed * maxDistLower / newDist;
  648. newDist = maxDistLower;
  649. }
  650. newDist = newDist * (dist < 0 ? -1 : 1);
  651. newTime = speed / deceleration;
  652. return { dist: newDist, time: m.round(newTime) };
  653. },
  654. _offset: function (el) {
  655. var left = -el.offsetLeft,
  656. top = -el.offsetTop;
  657. while (el = el.offsetParent) {
  658. left -= el.offsetLeft;
  659. top -= el.offsetTop;
  660. }
  661. if (el != this.wrapper) {
  662. left *= this.scale;
  663. top *= this.scale;
  664. }
  665. return { left: left, top: top };
  666. },
  667. _snap: function (x, y) {
  668. var that = this,
  669. i, l,
  670. page, time,
  671. sizeX, sizeY;
  672. // Check page X
  673. page = that.pagesX.length - 1;
  674. for (i=0, l=that.pagesX.length; i<l; i++) {
  675. if (x >= that.pagesX[i]) {
  676. page = i;
  677. break;
  678. }
  679. }
  680. if (page == that.currPageX && page > 0 && that.dirX < 0) page--;
  681. x = that.pagesX[page];
  682. sizeX = m.abs(x - that.pagesX[that.currPageX]);
  683. sizeX = sizeX ? m.abs(that.x - x) / sizeX * 500 : 0;
  684. that.currPageX = page;
  685. // Check page Y
  686. page = that.pagesY.length-1;
  687. for (i=0; i<page; i++) {
  688. if (y >= that.pagesY[i]) {
  689. page = i;
  690. break;
  691. }
  692. }
  693. if (page == that.currPageY && page > 0 && that.dirY < 0) page--;
  694. y = that.pagesY[page];
  695. sizeY = m.abs(y - that.pagesY[that.currPageY]);
  696. sizeY = sizeY ? m.abs(that.y - y) / sizeY * 500 : 0;
  697. that.currPageY = page;
  698. // Snap with constant speed (proportional duration)
  699. time = m.round(m.max(sizeX, sizeY)) || 200;
  700. return { x: x, y: y, time: time };
  701. },
  702. _bind: function (type, el, bubble) {
  703. (el || this.scroller).addEventListener(type, this, !!bubble);
  704. },
  705. _unbind: function (type, el, bubble) {
  706. (el || this.scroller).removeEventListener(type, this, !!bubble);
  707. },
  708. /**
  709. *
  710. * Public methods
  711. *
  712. */
  713. destroy: function () {
  714. var that = this;
  715. that.scroller.style[transform] = '';
  716. // Remove the scrollbars
  717. that.hScrollbar = false;
  718. that.vScrollbar = false;
  719. that._scrollbar('h');
  720. that._scrollbar('v');
  721. // Remove the event listeners
  722. that._unbind(RESIZE_EV, window);
  723. that._unbind(START_EV);
  724. that._unbind(MOVE_EV, window);
  725. that._unbind(END_EV, window);
  726. that._unbind(CANCEL_EV, window);
  727. if (!that.options.hasTouch) {
  728. that._unbind(WHEEL_EV);
  729. }
  730. if (that.options.useTransition) that._unbind(TRNEND_EV);
  731. if (that.options.checkDOMChanges) clearInterval(that.checkDOMTime);
  732. if (that.options.onDestroy) that.options.onDestroy.call(that);
  733. },
  734. refresh: function () {
  735. var that = this,
  736. offset,
  737. i, l,
  738. els,
  739. pos = 0,
  740. page = 0;
  741. if (that.scale < that.options.zoomMin) that.scale = that.options.zoomMin;
  742. that.wrapperW = that.wrapper.clientWidth || 1;
  743. that.wrapperH = that.wrapper.clientHeight || 1;
  744. that.minScrollY = -that.options.topOffset || 0;
  745. that.scrollerW = m.round(that.scroller.offsetWidth * that.scale);
  746. that.scrollerH = m.round((that.scroller.offsetHeight + that.minScrollY) * that.scale);
  747. that.maxScrollX = that.wrapperW - that.scrollerW;
  748. that.maxScrollY = that.wrapperH - that.scrollerH + that.minScrollY;
  749. that.dirX = 0;
  750. that.dirY = 0;
  751. if (that.options.onRefresh) that.options.onRefresh.call(that);
  752. that.hScroll = that.options.hScroll && that.maxScrollX < 0;
  753. that.vScroll = that.options.vScroll && (!that.options.bounceLock && !that.hScroll || that.scrollerH > that.wrapperH);
  754. that.hScrollbar = that.hScroll && that.options.hScrollbar;
  755. that.vScrollbar = that.vScroll && that.options.vScrollbar && that.scrollerH > that.wrapperH;
  756. offset = that._offset(that.wrapper);
  757. that.wrapperOffsetLeft = -offset.left;
  758. that.wrapperOffsetTop = -offset.top;
  759. // Prepare snap
  760. if (typeof that.options.snap == 'string') {
  761. that.pagesX = [];
  762. that.pagesY = [];
  763. els = that.scroller.querySelectorAll(that.options.snap);
  764. for (i=0, l=els.length; i<l; i++) {
  765. pos = that._offset(els[i]);
  766. pos.left += that.wrapperOffsetLeft;
  767. pos.top += that.wrapperOffsetTop;
  768. that.pagesX[i] = pos.left < that.maxScrollX ? that.maxScrollX : pos.left * that.scale;
  769. that.pagesY[i] = pos.top < that.maxScrollY ? that.maxScrollY : pos.top * that.scale;
  770. }
  771. } else if (that.options.snap) {
  772. that.pagesX = [];
  773. while (pos >= that.maxScrollX) {
  774. that.pagesX[page] = pos;
  775. pos = pos - that.wrapperW;
  776. page++;
  777. }
  778. if (that.maxScrollX%that.wrapperW) that.pagesX[that.pagesX.length] = that.maxScrollX - that.pagesX[that.pagesX.length-1] + that.pagesX[that.pagesX.length-1];
  779. pos = 0;
  780. page = 0;
  781. that.pagesY = [];
  782. while (pos >= that.maxScrollY) {
  783. that.pagesY[page] = pos;
  784. pos = pos - that.wrapperH;
  785. page++;
  786. }
  787. if (that.maxScrollY%that.wrapperH) that.pagesY[that.pagesY.length] = that.maxScrollY - that.pagesY[that.pagesY.length-1] + that.pagesY[that.pagesY.length-1];
  788. }
  789. // Prepare the scrollbars
  790. that._scrollbar('h');
  791. that._scrollbar('v');
  792. if (!that.zoomed) {
  793. that.scroller.style[transitionDuration] = '0';
  794. that._resetPos(400);
  795. }
  796. },
  797. scrollTo: function (x, y, time, relative) {
  798. var that = this,
  799. step = x,
  800. i, l;
  801. that.stop();
  802. if (!step.length) step = [{ x: x, y: y, time: time, relative: relative }];
  803. for (i=0, l=step.length; i<l; i++) {
  804. if (step[i].relative) { step[i].x = that.x - step[i].x; step[i].y = that.y - step[i].y; }
  805. that.steps.push({ x: step[i].x, y: step[i].y, time: step[i].time || 0 });
  806. }
  807. that._startAni();
  808. },
  809. scrollToElement: function (el, time) {
  810. var that = this, pos;
  811. el = el.nodeType ? el : that.scroller.querySelector(el);
  812. if (!el) return;
  813. pos = that._offset(el);
  814. pos.left += that.wrapperOffsetLeft;
  815. pos.top += that.wrapperOffsetTop;
  816. pos.left = pos.left > 0 ? 0 : pos.left < that.maxScrollX ? that.maxScrollX : pos.left;
  817. pos.top = pos.top > that.minScrollY ? that.minScrollY : pos.top < that.maxScrollY ? that.maxScrollY : pos.top;
  818. time = time === undefined ? m.max(m.abs(pos.left)*2, m.abs(pos.top)*2) : time;
  819. that.scrollTo(pos.left, pos.top, time);
  820. },
  821. scrollToPage: function (pageX, pageY, time) {
  822. var that = this, x, y;
  823. time = time === undefined ? 400 : time;
  824. if (that.options.onScrollStart) that.options.onScrollStart.call(that);
  825. if (that.options.snap) {
  826. pageX = pageX == 'next' ? that.currPageX+1 : pageX == 'prev' ? that.currPageX-1 : pageX;
  827. pageY = pageY == 'next' ? that.currPageY+1 : pageY == 'prev' ? that.currPageY-1 : pageY;
  828. pageX = pageX < 0 ? 0 : pageX > that.pagesX.length-1 ? that.pagesX.length-1 : pageX;
  829. pageY = pageY < 0 ? 0 : pageY > that.pagesY.length-1 ? that.pagesY.length-1 : pageY;
  830. that.currPageX = pageX;
  831. that.currPageY = pageY;
  832. x = that.pagesX[pageX];
  833. y = that.pagesY[pageY];
  834. } else {
  835. x = -that.wrapperW * pageX;
  836. y = -that.wrapperH * pageY;
  837. if (x < that.maxScrollX) x = that.maxScrollX;
  838. if (y < that.maxScrollY) y = that.maxScrollY;
  839. }
  840. that.scrollTo(x, y, time);
  841. },
  842. disable: function () {
  843. this.stop();
  844. this._resetPos(0);
  845. this.enabled = false;
  846. // If disabled after touchstart we make sure that there are no left over events
  847. this._unbind(MOVE_EV, window);
  848. this._unbind(END_EV, window);
  849. this._unbind(CANCEL_EV, window);
  850. },
  851. enable: function () {
  852. this.enabled = true;
  853. },
  854. stop: function () {
  855. if (this.options.useTransition) this._unbind(TRNEND_EV);
  856. else cancelFrame(this.aniTime);
  857. this.steps = [];
  858. this.moved = false;
  859. this.animating = false;
  860. },
  861. zoom: function (x, y, scale, time) {
  862. var that = this,
  863. relScale = scale / that.scale;
  864. if (!that.options.useTransform) return;
  865. that.zoomed = true;
  866. time = time === undefined ? 200 : time;
  867. x = x - that.wrapperOffsetLeft - that.x;
  868. y = y - that.wrapperOffsetTop - that.y;
  869. that.x = x - x * relScale + that.x;
  870. that.y = y - y * relScale + that.y;
  871. that.scale = scale;
  872. that.refresh();
  873. that.x = that.x > 0 ? 0 : that.x < that.maxScrollX ? that.maxScrollX : that.x;
  874. that.y = that.y > that.minScrollY ? that.minScrollY : that.y < that.maxScrollY ? that.maxScrollY : that.y;
  875. that.scroller.style[transitionDuration] = time + 'ms';
  876. that.scroller.style[transform] = 'translate(' + that.x + 'px,' + that.y + 'px) scale(' + scale + ')' + translateZ;
  877. that.zoomed = false;
  878. },
  879. isReady: function () {
  880. return !this.moved && !this.zoomed && !this.animating;
  881. }
  882. };
  883. function prefixStyle (style) {
  884. if ( vendor === '' ) return style;
  885. style = style.charAt(0).toUpperCase() + style.substr(1);
  886. return vendor + style;
  887. }
  888. dummyStyle = null; // for the sake of it
  889. if (typeof exports !== 'undefined') exports.iScroll = iScroll;
  890. else window.iScroll = iScroll;
  891. })(window, document);