PageRenderTime 303ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/src/iscroll.js

https://code.google.com/p/iscroll-js/
JavaScript | 1049 lines | 786 code | 190 blank | 73 comment | 254 complexity | 5362eba55798e45bdd7713ab8193755c MD5 | raw file
Possible License(s): JSON
  1. /**
  2. *
  3. * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  4. * iScroll v4.0 Beta 4
  5. * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  6. *
  7. * Copyright (c) 2010 Matteo Spinelli, http://cubiq.org/
  8. * Released under MIT license
  9. * http://cubiq.org/dropbox/mit-license.txt
  10. *
  11. * Last updated: 2011.03.10
  12. *
  13. * * * * * * * * * * * * * * * * * * * * * * * * * * * *
  14. *
  15. */
  16. (function(){
  17. function iScroll (el, options) {
  18. var that = this, doc = document, div, i;
  19. that.wrapper = typeof el == 'object' ? el : doc.getElementById(el);
  20. that.wrapper.style.overflow = 'hidden';
  21. that.scroller = that.wrapper.children[0];
  22. // Default options
  23. that.options = {
  24. HWTransition: true, // Experimental, internal use only
  25. HWCompositing: true, // Experimental, internal use only
  26. hScroll: true,
  27. vScroll: true,
  28. hScrollbar: true,
  29. vScrollbar: true,
  30. fixedScrollbar: isAndroid,
  31. fadeScrollbar: (isIDevice && has3d) || !hasTouch,
  32. hideScrollbar: isIDevice || !hasTouch,
  33. scrollbarClass: '',
  34. bounce: has3d,
  35. bounceLock: false,
  36. momentum: has3d,
  37. lockDirection: true,
  38. zoom: false,
  39. zoomMin: 1,
  40. zoomMax: 4,
  41. snap: false,
  42. pullToRefresh: false,
  43. pullDownLabel: ['Pull down to refresh...', 'Release to refresh...', 'Loading...'],
  44. pullUpLabel: ['Pull up to refresh...', 'Release to refresh...', 'Loading...'],
  45. onPullDown: function () {},
  46. onPullUp: function () {},
  47. onScrollStart: null,
  48. onScrollEnd: null,
  49. onZoomStart: null,
  50. onZoomEnd: null,
  51. checkDOMChange: false // Experimental
  52. };
  53. // User defined options
  54. for (i in options) {
  55. that.options[i] = options[i];
  56. }
  57. that.options.HWCompositing = that.options.HWCompositing && hasCompositing;
  58. that.options.HWTransition = that.options.HWTransition && hasCompositing;
  59. if (that.options.HWCompositing) {
  60. that.scroller.style.cssText += '-webkit-transition-property:-webkit-transform;-webkit-transform-origin:0 0;-webkit-transform:' + trnOpen + '0,0' + trnClose;
  61. } else {
  62. that.scroller.style.cssText += '-webkit-transition-property:top,left;-webkit-transform-origin:0 0;top:0;left:0';
  63. }
  64. if (that.options.HWTransition) {
  65. that.scroller.style.cssText += '-webkit-transition-timing-function:cubic-bezier(0.33,0.66,0.66,1);-webkit-transition-duration:0;';
  66. }
  67. that.options.hScrollbar = that.options.hScroll && that.options.hScrollbar;
  68. that.options.vScrollbar = that.options.vScroll && that.options.vScrollbar;
  69. that.pullDownToRefresh = that.options.pullToRefresh == 'down' || that.options.pullToRefresh == 'both';
  70. that.pullUpToRefresh = that.options.pullToRefresh == 'up' || that.options.pullToRefresh == 'both';
  71. if (that.pullDownToRefresh) {
  72. div = doc.createElement('div');
  73. div.className = 'iScrollPullDown';
  74. div.innerHTML = '<span class="iScrollPullDownIcon"></span><span class="iScrollPullDownLabel">' + that.options.pullDownLabel[0] + '</span>\n';
  75. that.scroller.insertBefore(div, that.scroller.children[0]);
  76. that.options.bounce = true;
  77. that.pullDownEl = div;
  78. that.pullDownLabel = div.getElementsByTagName('span')[1];
  79. }
  80. if (that.pullUpToRefresh) {
  81. div = doc.createElement('div');
  82. div.className = 'iScrollPullUp';
  83. div.innerHTML = '<span class="iScrollPullUpIcon"></span><span class="iScrollPullUpLabel">' + that.options.pullUpLabel[0] + '</span>\n';
  84. that.scroller.appendChild(div);
  85. that.options.bounce = true;
  86. that.pullUpEl = div;
  87. that.pullUpLabel = div.getElementsByTagName('span')[1];
  88. }
  89. that.refresh();
  90. that._bind(RESIZE_EV, window);
  91. that._bind(START_EV);
  92. /* that._bind(MOVE_EV);
  93. that._bind(END_EV);
  94. that._bind(CANCEL_EV);*/
  95. if (hasGesture && that.options.zoom) {
  96. that._bind('gesturestart');
  97. that.scroller.style.webkitTransform = that.scroller.style.webkitTransform + ' scale(1)';
  98. }
  99. if (!hasTouch) {
  100. that._bind('mousewheel');
  101. }
  102. if (that.options.checkDOMChange) {
  103. that.DOMChangeInterval = setInterval(function () { that._checkSize(); }, 250);
  104. }
  105. }
  106. iScroll.prototype = {
  107. x: 0, y: 0,
  108. currPageX: 0, currPageY: 0,
  109. pagesX: [], pagesY: [],
  110. offsetBottom: 0,
  111. offsetTop: 0,
  112. scale: 1, lastScale: 1,
  113. contentReady: true,
  114. handleEvent: function (e) {
  115. var that = this;
  116. switch(e.type) {
  117. case START_EV: that._start(e); break;
  118. case MOVE_EV: that._move(e); break;
  119. case END_EV:
  120. case CANCEL_EV: that._end(e); break;
  121. case 'webkitTransitionEnd': that._transitionEnd(e); break;
  122. case RESIZE_EV: that._resize(); break;
  123. case 'gesturestart': that._gestStart(e); break;
  124. case 'gesturechange': that._gestChange(e); break;
  125. case 'gestureend':
  126. case 'gesturecancel': that._gestEnd(e); break;
  127. case 'mousewheel': that._wheel(e); break;
  128. }
  129. },
  130. _scrollbar: function (dir) {
  131. var that = this,
  132. doc = document,
  133. bar;
  134. if (!that[dir + 'Scrollbar']) {
  135. if (that[dir + 'ScrollbarWrapper']) {
  136. that[dir + 'ScrollbarIndicator'].style.webkitTransform = ''; // Should free some mem
  137. that[dir + 'ScrollbarWrapper'].parentNode.removeChild(that[dir + 'ScrollbarWrapper']);
  138. that[dir + 'ScrollbarWrapper'] = null;
  139. that[dir + 'ScrollbarIndicator'] = null;
  140. }
  141. return;
  142. }
  143. if (!that[dir + 'ScrollbarWrapper']) {
  144. // Create the scrollbar wrapper
  145. bar = doc.createElement('div');
  146. if (that.options.scrollbarClass) {
  147. bar.className = that.options.scrollbarClass + dir.toUpperCase();
  148. } else {
  149. bar.style.cssText = 'position:absolute;z-index:100;' + (dir == 'h' ? 'height:7px;bottom:1px;left:2px;right:7px' : 'width:7px;bottom:7px;top:2px;right:1px');
  150. }
  151. bar.style.cssText += 'pointer-events:none;-webkit-transition-property:opacity;-webkit-transition-duration:' + (that.options.fadeScrollbar ? '350ms' : '0') + ';overflow:hidden;opacity:' + (that.options.hideScrollbar ? '0' : '1');
  152. that.wrapper.appendChild(bar);
  153. that[dir + 'ScrollbarWrapper'] = bar;
  154. // Create the scrollbar indicator
  155. bar = doc.createElement('div');
  156. if (!that.options.scrollbarClass) {
  157. bar.style.cssText = 'position:absolute;z-index:100;background:rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.9);-webkit-background-clip:padding-box;-webkit-box-sizing:border-box;' + (dir == 'h' ? 'height:100%;-webkit-border-radius:4px 3px;' : 'width:100%;-webkit-border-radius:3px 4px;');
  158. }
  159. bar.style.cssText += 'pointer-events:none;-webkit-transition-property:-webkit-transform;-webkit-transition-timing-function:cubic-bezier(0.33,0.66,0.66,1);-webkit-transition-duration:0;-webkit-transform:' + trnOpen + '0,0' + trnClose;
  160. that[dir + 'ScrollbarWrapper'].appendChild(bar);
  161. that[dir + 'ScrollbarIndicator'] = bar;
  162. }
  163. if (dir == 'h') {
  164. that.hScrollbarSize = that.hScrollbarWrapper.clientWidth;
  165. that.hScrollbarIndicatorSize = m.max(m.round(that.hScrollbarSize * that.hScrollbarSize / that.scrollerW), 8);
  166. that.hScrollbarIndicator.style.width = that.hScrollbarIndicatorSize + 'px';
  167. that.hScrollbarMaxScroll = that.hScrollbarSize - that.hScrollbarIndicatorSize;
  168. that.hScrollbarProp = that.hScrollbarMaxScroll / that.maxScrollX;
  169. } else {
  170. that.vScrollbarSize = that.vScrollbarWrapper.clientHeight;
  171. that.vScrollbarIndicatorSize = m.max(m.round(that.vScrollbarSize * that.vScrollbarSize / that.scrollerH), 8);
  172. that.vScrollbarIndicator.style.height = that.vScrollbarIndicatorSize + 'px';
  173. that.vScrollbarMaxScroll = that.vScrollbarSize - that.vScrollbarIndicatorSize;
  174. that.vScrollbarProp = that.vScrollbarMaxScroll / that.maxScrollY;
  175. }
  176. // Reset position
  177. that._indicatorPos(dir, true);
  178. },
  179. _resize: function () {
  180. var that = this;
  181. //if (that.options.momentum) that._unbind('webkitTransitionEnd');
  182. setTimeout(function () {
  183. that.refresh();
  184. }, 0);
  185. },
  186. _checkSize: function () {
  187. var that = this,
  188. scrollerW,
  189. scrollerH;
  190. if (that.moved || that.zoomed || !that.contentReady) return;
  191. scrollerW = m.round(that.scroller.offsetWidth * that.scale),
  192. scrollerH = m.round((that.scroller.offsetHeight - that.offsetBottom - that.offsetTop) * that.scale);
  193. if (scrollerW == that.scrollerW && scrollerH == that.scrollerH) return;
  194. that.refresh();
  195. },
  196. _pos: function (x, y) {
  197. var that = this;
  198. that.x = that.hScroll ? x : 0;
  199. that.y = that.vScroll ? y : 0;
  200. that.scroller.style.webkitTransform = trnOpen + that.x + 'px,' + that.y + 'px' + trnClose + ' scale(' + that.scale + ')';
  201. // that.scroller.style.left = that.x + 'px';
  202. // that.scroller.style.top = that.y + 'px';
  203. that._indicatorPos('h');
  204. that._indicatorPos('v');
  205. },
  206. _indicatorPos: function (dir, hidden) {
  207. var that = this,
  208. pos = dir == 'h' ? that.x : that.y;
  209. if (!that[dir + 'Scrollbar']) return;
  210. pos = that[dir + 'ScrollbarProp'] * pos;
  211. if (pos < 0) {
  212. pos = that.options.fixedScrollbar ? 0 : pos + pos*3;
  213. if (that[dir + 'ScrollbarIndicatorSize'] + pos < 9) pos = -that[dir + 'ScrollbarIndicatorSize'] + 8;
  214. } else if (pos > that[dir + 'ScrollbarMaxScroll']) {
  215. pos = that.options.fixedScrollbar ? that[dir + 'ScrollbarMaxScroll'] : pos + (pos - that[dir + 'ScrollbarMaxScroll'])*3;
  216. if (that[dir + 'ScrollbarIndicatorSize'] + that[dir + 'ScrollbarMaxScroll'] - pos < 9) pos = that[dir + 'ScrollbarIndicatorSize'] + that[dir + 'ScrollbarMaxScroll'] - 8;
  217. }
  218. that[dir + 'ScrollbarWrapper'].style.webkitTransitionDelay = '0';
  219. that[dir + 'ScrollbarWrapper'].style.opacity = hidden && that.options.hideScrollbar ? '0' : '1';
  220. that[dir + 'ScrollbarIndicator'].style.webkitTransform = trnOpen + (dir == 'h' ? pos + 'px,0' : '0,' + pos + 'px') + trnClose;
  221. },
  222. _transitionTime: function (time) {
  223. var that = this;
  224. time += 'ms';
  225. that.scroller.style.webkitTransitionDuration = time;
  226. if (that.hScrollbar) that.hScrollbarIndicator.style.webkitTransitionDuration = time;
  227. if (that.vScrollbar) that.vScrollbarIndicator.style.webkitTransitionDuration = time;
  228. },
  229. _start: function (e) {
  230. var that = this,
  231. point = hasTouch ? e.changedTouches[0] : e,
  232. matrix;
  233. that.moved = false;
  234. e.preventDefault();
  235. if (hasTouch && e.touches.length == 2 && that.options.zoom && hasGesture && !that.zoomed) {
  236. that.originX = m.abs(e.touches[0].pageX + e.touches[1].pageX - that.wrapperOffsetLeft*2) / 2 - that.x;
  237. that.originY = m.abs(e.touches[0].pageY + e.touches[1].pageY - that.wrapperOffsetTop*2) / 2 - that.y;
  238. }
  239. that.moved = false;
  240. that.distX = 0;
  241. that.distY = 0;
  242. that.absDistX = 0;
  243. that.absDistY = 0;
  244. that.dirX = 0;
  245. that.dirY = 0;
  246. that.returnTime = 0;
  247. that._transitionTime(0);
  248. if (that.options.momentum) {
  249. if (that.scrollInterval) {
  250. clearInterval(that.scrollInterval);
  251. that.scrollInterval = null;
  252. }
  253. if (that.options.HWCompositing) {
  254. matrix = new WebKitCSSMatrix(window.getComputedStyle(that.scroller, null).webkitTransform);
  255. if (matrix.m41 != that.x || matrix.m42 != that.y) {
  256. that._unbind('webkitTransitionEnd');
  257. that._pos(matrix.m41, matrix.m42);
  258. }
  259. } else {
  260. matrix = window.getComputedStyle(that.scroller, null);
  261. if (that.x + 'px' != matrix.left || that.y + 'px' != matrix.top) {
  262. that._unbind('webkitTransitionEnd');
  263. that._pos(matrix.left.replace(/[^0-9]/g)*1, matrix.top.replace(/[^0-9]/g)*1);
  264. }
  265. }
  266. }
  267. that.scroller.style.webkitTransitionTimingFunction = 'cubic-bezier(0.33,0.66,0.66,1)';
  268. if (that.hScrollbar) that.hScrollbarIndicator.style.webkitTransitionTimingFunction = 'cubic-bezier(0.33,0.66,0.66,1)';
  269. if (that.vScrollbar) that.vScrollbarIndicator.style.webkitTransitionTimingFunction = 'cubic-bezier(0.33,0.66,0.66,1)';
  270. that.startX = that.x;
  271. that.startY = that.y;
  272. that.pointX = point.pageX;
  273. that.pointY = point.pageY;
  274. that.startTime = e.timeStamp;
  275. if (that.options.onScrollStart) that.options.onScrollStart.call(that);
  276. // Registering/unregistering of events is done to preserve resources on Android
  277. // setTimeout(function () {
  278. // that._unbind(START_EV);
  279. that._bind(MOVE_EV);
  280. that._bind(END_EV);
  281. that._bind(CANCEL_EV);
  282. // }, 0);
  283. },
  284. _move: function (e) {
  285. if (hasTouch && e.touches.length > 1) return;
  286. var that = this,
  287. point = hasTouch ? e.changedTouches[0] : e,
  288. deltaX = point.pageX - that.pointX,
  289. deltaY = point.pageY - that.pointY,
  290. newX = that.x + deltaX,
  291. newY = that.y + deltaY;
  292. e.preventDefault();
  293. that.pointX = point.pageX;
  294. that.pointY = point.pageY;
  295. // Slow down if outside of the boundaries
  296. if (newX > 0 || newX < that.maxScrollX) {
  297. newX = that.options.bounce ? that.x + (deltaX / 2.4) : newX >= 0 || that.maxScrollX >= 0 ? 0 : that.maxScrollX;
  298. }
  299. if (newY > 0 || newY < that.maxScrollY) {
  300. newY = that.options.bounce ? that.y + (deltaY / 2.4) : newY >= 0 || that.maxScrollY >= 0 ? 0 : that.maxScrollY;
  301. // Pull down to refresh
  302. if (that.options.pullToRefresh && that.contentReady) {
  303. if (that.pullDownToRefresh && newY > that.offsetBottom) {
  304. that.pullDownEl.className = 'iScrollPullDown flip';
  305. that.pullDownLabel.innerText = that.options.pullDownLabel[1];
  306. } else if (that.pullDownToRefresh && that.pullDownEl.className.match('flip')) {
  307. that.pullDownEl.className = 'iScrollPullDown';
  308. that.pullDownLabel.innerText = that.options.pullDownLabel[0];
  309. }
  310. if (that.pullUpToRefresh && newY < that.maxScrollY - that.offsetTop) {
  311. that.pullUpEl.className = 'iScrollPullUp flip';
  312. that.pullUpLabel.innerText = that.options.pullUpLabel[1];
  313. } else if (that.pullUpToRefresh && that.pullUpEl.className.match('flip')) {
  314. that.pullUpEl.className = 'iScrollPullUp';
  315. that.pullUpLabel.innerText = that.options.pullUpLabel[0];
  316. }
  317. }
  318. }
  319. if (that.absDistX < 4 && that.absDistY < 4) {
  320. that.distX += deltaX;
  321. that.distY += deltaY;
  322. that.absDistX = m.abs(that.distX);
  323. that.absDistY = m.abs(that.distY);
  324. return;
  325. }
  326. // Lock direction
  327. if (that.options.lockDirection) {
  328. if (that.absDistX > that.absDistY+3) {
  329. newY = that.y;
  330. deltaY = 0;
  331. } else if (that.absDistY > that.absDistX+3) {
  332. newX = that.x;
  333. deltaX = 0;
  334. }
  335. }
  336. that.moved = true;
  337. that._pos(newX, newY);
  338. that.dirX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0;
  339. that.dirY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0;
  340. if (e.timeStamp - that.startTime > 300) {
  341. that.startTime = e.timeStamp;
  342. that.startX = that.x;
  343. that.startY = that.y;
  344. }
  345. },
  346. _end: function (e) {
  347. if (hasTouch && e.touches.length != 0) return;
  348. var that = this,
  349. point = hasTouch ? e.changedTouches[0] : e,
  350. target, ev,
  351. momentumX = { dist:0, time:0 },
  352. momentumY = { dist:0, time:0 },
  353. duration = e.timeStamp - that.startTime,
  354. newPosX = that.x, newPosY = that.y,
  355. newDuration,
  356. snap;
  357. // that._bind(START_EV);
  358. that._unbind(MOVE_EV);
  359. that._unbind(END_EV);
  360. that._unbind(CANCEL_EV);
  361. if (that.zoomed) return;
  362. if (!that.moved) {
  363. if (hasTouch) {
  364. if (that.doubleTapTimer && that.options.zoom) {
  365. // Double tapped
  366. clearTimeout(that.doubleTapTimer);
  367. that.doubleTapTimer = null;
  368. that.zoom(that.pointX, that.pointY, that.scale == 1 ? 2 : 1);
  369. } else {
  370. that.doubleTapTimer = setTimeout(function () {
  371. that.doubleTapTimer = null;
  372. // Find the last touched element
  373. target = point.target;
  374. while (target.nodeType != 1) {
  375. target = target.parentNode;
  376. }
  377. ev = document.createEvent('MouseEvents');
  378. ev.initMouseEvent('click', true, true, e.view, 1,
  379. point.screenX, point.screenY, point.clientX, point.clientY,
  380. e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
  381. 0, null);
  382. ev._fake = true;
  383. target.dispatchEvent(ev);
  384. }, that.options.zoom ? 250 : 0);
  385. }
  386. }
  387. that._resetPos();
  388. return;
  389. }
  390. if (that.pullDownToRefresh && that.contentReady && that.pullDownEl.className.match('flip')) {
  391. that.pullDownEl.className = 'iScrollPullDown loading';
  392. that.pullDownLabel.innerText = that.options.pullDownLabel[2];
  393. that.scroller.style.marginTop = '0';
  394. that.offsetBottom = 0;
  395. that.refresh();
  396. that.contentReady = false;
  397. that.options.onPullDown();
  398. }
  399. if (that.pullUpToRefresh && that.contentReady && that.pullUpEl.className.match('flip')) {
  400. that.pullUpEl.className = 'iScrollPullUp loading';
  401. that.pullUpLabel.innerText = that.options.pullUpLabel[2];
  402. that.scroller.style.marginBottom = '0';
  403. that.offsetTop = 0;
  404. that.refresh();
  405. that.contentReady = false;
  406. that.options.onPullUp();
  407. }
  408. if (duration < 300 && that.options.momentum) {
  409. momentumX = newPosX ? that._momentum(newPosX - that.startX, duration, -that.x, that.scrollerW - that.wrapperW + that.x, that.options.bounce ? that.wrapperW : 0) : momentumX;
  410. momentumY = newPosY ? that._momentum(newPosY - that.startY, duration, -that.y, (that.maxScrollY < 0 ? that.scrollerH - that.wrapperH + that.y : 0), that.options.bounce ? that.wrapperH : 0) : momentumY;
  411. newPosX = that.x + momentumX.dist;
  412. newPosY = that.y + momentumY.dist;
  413. if ((that.x > 0 && newPosX > 0) || (that.x < that.maxScrollX && newPosX < that.maxScrollX)) momentumX = { dist:0, time:0 };
  414. if ((that.y > 0 && newPosY > 0) || (that.y < that.maxScrollY && newPosY < that.maxScrollY)) momentumY = { dist:0, time:0 };
  415. }
  416. if (momentumX.dist || momentumY.dist) {
  417. newDuration = m.max(m.max(momentumX.time, momentumY.time), 10);
  418. // Do we need to snap?
  419. if (that.options.snap) {
  420. snap = that._snap(newPosX, newPosY);
  421. newPosX = snap.x;
  422. newPosY = snap.y;
  423. newDuration = m.max(snap.time, newDuration);
  424. }
  425. /* if (newPosX > 0 || newPosX < that.maxScrollX || newPosY > 0 || newPosY < that.maxScrollY) {
  426. // Subtle change of scroller motion
  427. that.scroller.style.webkitTransitionTimingFunction = 'cubic-bezier(0.33,0.66,0.5,1)';
  428. if (that.hScrollbar) that.hScrollbarIndicator.style.webkitTransitionTimingFunction = 'cubic-bezier(0.33,0.66,0.5,1)';
  429. if (that.vScrollbar) that.vScrollbarIndicator.style.webkitTransitionTimingFunction = 'cubic-bezier(0.33,0.66,0.5,1)';
  430. }*/
  431. that.scrollTo(newPosX, newPosY, newDuration);
  432. return;
  433. }
  434. // Do we need to snap?
  435. if (that.options.snap) {
  436. snap = that._snap(that.x, that.y);
  437. if (snap.x != that.x || snap.y != that.y) {
  438. that.scrollTo(snap.x, snap.y, snap.time);
  439. }
  440. return;
  441. }
  442. that._resetPos();
  443. },
  444. _resetPos: function (time) {
  445. var that = this,
  446. resetX = that.x,
  447. resetY = that.y;
  448. if (that.x >= 0) resetX = 0;
  449. else if (that.x < that.maxScrollX) resetX = that.maxScrollX;
  450. if (that.y >= 0 || that.maxScrollY > 0) resetY = 0;
  451. else if (that.y < that.maxScrollY) resetY = that.maxScrollY;
  452. if (resetX == that.x && resetY == that.y) {
  453. if (that.moved) {
  454. if (that.options.onScrollEnd) that.options.onScrollEnd.call(that); // Execute custom code on scroll end
  455. that.moved = false;
  456. }
  457. if (that.zoomed) {
  458. if (that.options.onZoomEnd) that.options.onZoomEnd.call(that); // Execute custom code on scroll end
  459. that.zoomed = false;
  460. }
  461. if (that.hScrollbar && that.options.hideScrollbar) {
  462. that.hScrollbarWrapper.style.webkitTransitionDelay = '300ms';
  463. that.hScrollbarWrapper.style.opacity = '0';
  464. }
  465. if (that.vScrollbar && that.options.hideScrollbar) {
  466. that.vScrollbarWrapper.style.webkitTransitionDelay = '300ms';
  467. that.vScrollbarWrapper.style.opacity = '0';
  468. }
  469. return;
  470. }
  471. if (time === undefined) time = 200;
  472. // Invert ease
  473. if (time) {
  474. that.scroller.style.webkitTransitionTimingFunction = 'cubic-bezier(0.33,0.0,0.33,1)';
  475. if (that.hScrollbar) that.hScrollbarIndicator.style.webkitTransitionTimingFunction = 'cubic-bezier(0.33,0.0,0.33,1)';
  476. if (that.vScrollbar) that.vScrollbarIndicator.style.webkitTransitionTimingFunction = 'cubic-bezier(0.33,0.0,0.33,1)';
  477. }
  478. that.scrollTo(resetX, resetY, time);
  479. },
  480. _timedScroll: function (destX, destY, runtime) {
  481. var that = this,
  482. startX = that.x, startY = that.y,
  483. startTime = (new Date).getTime(),
  484. easeOut;
  485. that._transitionTime(0);
  486. if (that.scrollInterval) {
  487. clearInterval(that.scrollInterval);
  488. that.scrollInterval = null;
  489. }
  490. that.scrollInterval = setInterval(function () {
  491. var now = (new Date).getTime(),
  492. newX, newY;
  493. if (now >= startTime + runtime) {
  494. clearInterval(that.scrollInterval);
  495. that.scrollInterval = null;
  496. that._pos(destX, destY);
  497. that._transitionEnd();
  498. return;
  499. }
  500. now = (now - startTime) / runtime - 1;
  501. easeOut = m.sqrt(1 - now * now);
  502. newX = (destX - startX) * easeOut + startX;
  503. newY = (destY - startY) * easeOut + startY;
  504. that._pos(newX, newY);
  505. }, 20);
  506. },
  507. _transitionEnd: function (e) {
  508. var that = this;
  509. if (e) e.stopPropagation();
  510. that._unbind('webkitTransitionEnd');
  511. that._resetPos(that.returnTime);
  512. that.returnTime = 0;
  513. },
  514. /**
  515. *
  516. * Gestures
  517. *
  518. */
  519. _gestStart: function (e) {
  520. var that = this;
  521. that._transitionTime(0);
  522. that.lastScale = 1;
  523. if (that.options.onZoomStart) that.options.onZoomStart.call(that);
  524. that._unbind('gesturestart');
  525. that._bind('gesturechange');
  526. that._bind('gestureend');
  527. that._bind('gesturecancel');
  528. },
  529. _gestChange: function (e) {
  530. var that = this,
  531. scale = that.scale * e.scale,
  532. x, y, relScale;
  533. that.zoomed = true;
  534. if (scale < that.options.zoomMin) scale = that.options.zoomMin;
  535. else if (scale > that.options.zoomMax) scale = that.options.zoomMax;
  536. relScale = scale / that.scale;
  537. x = that.originX - that.originX * relScale + that.x;
  538. y = that.originY - that.originY * relScale + that.y;
  539. that.scroller.style.webkitTransform = trnOpen + x + 'px,' + y + 'px' + trnClose + ' scale(' + scale + ')';
  540. that.lastScale = relScale;
  541. },
  542. _gestEnd: function (e) {
  543. var that = this,
  544. scale = that.scale,
  545. lastScale = that.lastScale;
  546. that.scale = scale * lastScale;
  547. if (that.scale < that.options.zoomMin + 0.05) that.scale = that.options.zoomMin;
  548. else if (that.scale > that.options.zoomMax - 0.05) that.scale = that.options.zoomMax;
  549. lastScale = that.scale / scale;
  550. that.x = that.originX - that.originX * lastScale + that.x;
  551. that.y = that.originY - that.originY * lastScale + that.y;
  552. that.scroller.style.webkitTransform = trnOpen + that.x + 'px,' + that.y + 'px' + trnClose + ' scale(' + that.scale + ')';
  553. setTimeout(function () {
  554. that.refresh();
  555. }, 0);
  556. that._bind('gesturestart');
  557. that._unbind('gesturechange');
  558. that._unbind('gestureend');
  559. that._unbind('gesturecancel');
  560. },
  561. _wheel: function (e) {
  562. var that = this,
  563. deltaX = that.x + e.wheelDeltaX / 12,
  564. deltaY = that.y + e.wheelDeltaY / 12;
  565. if (deltaX > 0) deltaX = 0;
  566. else if (deltaX < that.maxScrollX) deltaX = that.maxScrollX;
  567. if (deltaY > 0) deltaY = 0;
  568. else if (deltaY < that.maxScrollY) deltaY = that.maxScrollY;
  569. that.scrollTo(deltaX, deltaY, 0);
  570. },
  571. /**
  572. *
  573. * Utilities
  574. *
  575. */
  576. _momentum: function (dist, time, maxDistUpper, maxDistLower, size) {
  577. var that = this,
  578. deceleration = 0.0006,
  579. speed = m.abs(dist) / time,
  580. newDist = (speed * speed) / (2 * deceleration),
  581. newTime = 0, outsideDist = 0;
  582. // Proportinally reduce speed if we are outside of the boundaries
  583. if (dist > 0 && newDist > maxDistUpper) {
  584. outsideDist = size / (6 / (newDist / speed * deceleration));
  585. maxDistUpper = maxDistUpper + outsideDist;
  586. that.returnTime = 800 / size * outsideDist + 100;
  587. speed = speed * maxDistUpper / newDist;
  588. newDist = maxDistUpper;
  589. } else if (dist < 0 && newDist > maxDistLower) {
  590. outsideDist = size / (6 / (newDist / speed * deceleration));
  591. maxDistLower = maxDistLower + outsideDist;
  592. that.returnTime = 800 / size * outsideDist + 100;
  593. speed = speed * maxDistLower / newDist;
  594. newDist = maxDistLower;
  595. }
  596. newDist = newDist * (dist < 0 ? -1 : 1);
  597. newTime = speed / deceleration;
  598. return { dist: newDist, time: m.round(newTime) };
  599. },
  600. _offset: function (el, tree) {
  601. var left = -el.offsetLeft,
  602. top = -el.offsetTop;
  603. if (!tree) return { x: left, y: top };
  604. while (el = el.offsetParent) {
  605. left -= el.offsetLeft;
  606. top -= el.offsetTop;
  607. }
  608. return { x: left, y: top };
  609. },
  610. _snap: function (x, y) {
  611. var that = this,
  612. i, l,
  613. page, time,
  614. sizeX, sizeY;
  615. // Check page X
  616. page = that.pagesX.length-1;
  617. for (i=0, l=that.pagesX.length; i<l; i++) {
  618. if (x >= that.pagesX[i]) {
  619. page = i;
  620. break;
  621. }
  622. }
  623. if (page == that.currPageX && page > 0 && that.dirX < 0) page--;
  624. x = that.pagesX[page];
  625. sizeX = m.abs(x - that.pagesX[that.currPageX]);
  626. sizeX = sizeX ? m.abs(that.x - x) / sizeX * 500 : 0;
  627. that.currPageX = page;
  628. // Check page Y
  629. page = that.pagesY.length-1;
  630. for (i=0; i<page; i++) {
  631. if (y >= that.pagesY[i]) {
  632. page = i;
  633. break;
  634. }
  635. }
  636. if (page == that.currPageY && page > 0 && that.dirY < 0) page--;
  637. y = that.pagesY[page];
  638. sizeY = m.abs(y - that.pagesY[that.currPageY]);
  639. sizeY = sizeY ? m.abs(that.y - y) / sizeY * 500 : 0;
  640. that.currPageY = page;
  641. // Snap with constant speed (proportional duration)
  642. time = m.round(m.max(sizeX, sizeY)) || 200;
  643. return { x: x, y: y, time: time };
  644. },
  645. _bind: function (type, el) {
  646. (el || this.scroller).addEventListener(type, this, false);
  647. },
  648. _unbind: function (type, el) {
  649. (el || this.scroller).removeEventListener(type, this, false);
  650. },
  651. /**
  652. *
  653. * Public methods
  654. *
  655. */
  656. destroy: function () {
  657. var that = this;
  658. if (that.options.checkDOMChange) clearTimeout(that.DOMChangeInterval);
  659. // Remove pull to refresh
  660. if (that.pullDownToRefresh) {
  661. that.pullDownEl.parentNode.removeChild(that.pullDownEl);
  662. }
  663. if (that.pullUpToRefresh) {
  664. that.pullUpEl.parentNode.removeChild(that.pullUpEl);
  665. }
  666. // Remove the scrollbars
  667. that.hScrollbar = false;
  668. that.vScrollbar = false;
  669. that._scrollbar('h');
  670. that._scrollbar('v');
  671. // Free some mem
  672. that.scroller.style.webkitTransform = '';
  673. // Remove the event listeners
  674. that._unbind('webkitTransitionEnd');
  675. that._unbind(RESIZE_EV);
  676. that._unbind(START_EV);
  677. that._unbind(MOVE_EV);
  678. that._unbind(END_EV);
  679. that._unbind(CANCEL_EV);
  680. if (that.options.zoom) {
  681. that._unbind('gesturestart');
  682. that._unbind('gesturechange');
  683. that._unbind('gestureend');
  684. that._unbind('gesturecancel');
  685. }
  686. },
  687. refresh: function () {
  688. var that = this,
  689. pos = 0, page = 0,
  690. i, l, els,
  691. oldHeight, offsets,
  692. loading;
  693. if (that.pullDownToRefresh) {
  694. loading = that.pullDownEl.className.match('loading');
  695. if (loading && !that.contentReady) {
  696. oldHeight = that.scrollerH;
  697. that.contentReady = true;
  698. that.pullDownEl.className = 'iScrollPullDown';
  699. that.pullDownLabel.innerText = that.options.pullDownLabel[0];
  700. that.offsetBottom = that.pullDownEl.offsetHeight;
  701. that.scroller.style.marginTop = -that.offsetBottom + 'px';
  702. } else if (!loading) {
  703. that.offsetBottom = that.pullDownEl.offsetHeight;
  704. that.scroller.style.marginTop = -that.offsetBottom + 'px';
  705. }
  706. }
  707. if (that.pullUpToRefresh) {
  708. loading = that.pullUpEl.className.match('loading');
  709. if (loading && !that.contentReady) {
  710. oldHeight = that.scrollerH;
  711. that.contentReady = true;
  712. that.pullUpEl.className = 'iScrollPullUp';
  713. that.pullUpLabel.innerText = that.options.pullUpLabel[0];
  714. that.offsetTop = that.pullUpEl.offsetHeight;
  715. that.scroller.style.marginBottom = -that.offsetTop + 'px';
  716. } else if (!loading) {
  717. that.offsetTop = that.pullUpEl.offsetHeight;
  718. that.scroller.style.marginBottom = -that.offsetTop + 'px';
  719. }
  720. }
  721. that.wrapperW = that.wrapper.clientWidth;
  722. that.wrapperH = that.wrapper.clientHeight;
  723. that.scrollerW = m.round(that.scroller.offsetWidth * that.scale);
  724. that.scrollerH = m.round((that.scroller.offsetHeight - that.offsetBottom - that.offsetTop) * that.scale);
  725. that.maxScrollX = that.wrapperW - that.scrollerW;
  726. that.maxScrollY = that.wrapperH - that.scrollerH;
  727. that.dirX = 0;
  728. that.dirY = 0;
  729. that._transitionTime(0);
  730. that.hScroll = that.options.hScroll && that.maxScrollX < 0;
  731. that.vScroll = that.options.vScroll && (!that.options.bounceLock && !that.hScroll || that.scrollerH > that.wrapperH);
  732. that.hScrollbar = that.hScroll && that.options.hScrollbar;
  733. that.vScrollbar = that.vScroll && that.options.vScrollbar && that.scrollerH > that.wrapperH;
  734. // Prepare the scrollbars
  735. that._scrollbar('h');
  736. that._scrollbar('v');
  737. // Snap
  738. if (typeof that.options.snap == 'string') {
  739. that.pagesX = [];
  740. that.pagesY = [];
  741. els = that.scroller.querySelectorAll(that.options.snap);
  742. for (i=0, l=els.length; i<l; i++) {
  743. pos = that._offset(els[i]);
  744. that.pagesX[i] = pos.x < that.maxScrollX ? that.maxScrollX : pos.x * that.scale;
  745. that.pagesY[i] = pos.y < that.maxScrollY ? that.maxScrollY : pos.y * that.scale;
  746. }
  747. } else if (that.options.snap) {
  748. that.pagesX = [];
  749. while (pos >= that.maxScrollX) {
  750. that.pagesX[page] = pos;
  751. pos = pos - that.wrapperW;
  752. page++;
  753. }
  754. if (that.maxScrollX%that.wrapperW) that.pagesX[that.pagesX.length] = that.maxScrollX - that.pagesX[that.pagesX.length-1] + that.pagesX[that.pagesX.length-1];
  755. pos = 0;
  756. page = 0;
  757. that.pagesY = [];
  758. while (pos >= that.maxScrollY) {
  759. that.pagesY[page] = pos;
  760. pos = pos - that.wrapperH;
  761. page++;
  762. }
  763. if (that.maxScrollY%that.wrapperH) that.pagesY[that.pagesY.length] = that.maxScrollY - that.pagesY[that.pagesY.length-1] + that.pagesY[that.pagesY.length-1];
  764. }
  765. // Recalculate wrapper offsets
  766. if (that.options.zoom) {
  767. offsets = that._offset(that.wrapper, true);
  768. that.wrapperOffsetLeft = -offsets.x;
  769. that.wrapperOffsetTop = -offsets.y;
  770. }
  771. if (oldHeight && that.y == 0) {
  772. oldHeight = oldHeight - that.scrollerH + that.y;
  773. that.scrollTo(0, oldHeight, 0);
  774. }
  775. that._resetPos();
  776. },
  777. scrollTo: function (x, y, time, relative) {
  778. var that = this;
  779. if (relative) {
  780. x = that.x - x;
  781. y = that.y - y;
  782. }
  783. time = !time || (m.round(that.x) == m.round(x) && m.round(that.y) == m.round(y)) ? 0 : time;
  784. that.moved = true;
  785. if (!that.options.HWTransition) {
  786. that._timedScroll(x, y, time);
  787. return;
  788. }
  789. if (time) that._bind('webkitTransitionEnd');
  790. that._transitionTime(time);
  791. that._pos(x, y);
  792. if (!time) setTimeout(function () { that._transitionEnd(); }, 0);
  793. },
  794. scrollToElement: function (el, time) {
  795. var that = this, pos;
  796. el = el.nodeType ? el : that.scroller.querySelector(el);
  797. if (!el) return;
  798. pos = that._offset(el);
  799. pos.x = pos.x > 0 ? 0 : pos.x < that.maxScrollX ? that.maxScrollX : pos.x;
  800. pos.y = pos.y > 0 ? 0 : pos.y < that.maxScrollY ? that.maxScrollY : pos.y;
  801. time = time === undefined ? m.max(m.abs(pos.x)*2, m.abs(pos.y)*2) : time;
  802. that.scrollTo(pos.x, pos.y, time);
  803. },
  804. scrollToPage: function (pageX, pageY, time) {
  805. var that = this, x, y;
  806. if (that.options.snap) {
  807. pageX = pageX == 'next' ? that.currPageX+1 : pageX == 'prev' ? that.currPageX-1 : pageX;
  808. pageY = pageY == 'next' ? that.currPageY+1 : pageY == 'prev' ? that.currPageY-1 : pageY;
  809. pageX = pageX < 0 ? 0 : pageX > that.pagesX.length-1 ? that.pagesX.length-1 : pageX;
  810. pageY = pageY < 0 ? 0 : pageY > that.pagesY.length-1 ? that.pagesY.length-1 : pageY;
  811. that.currPageX = pageX;
  812. that.currPageY = pageY;
  813. x = that.pagesX[pageX];
  814. y = that.pagesY[pageY];
  815. } else {
  816. x = -that.wrapperW * pageX;
  817. y = -that.wrapperH * pageY;
  818. if (x < that.maxScrollX) x = that.maxScrollX;
  819. if (y < that.maxScrollY) y = that.maxScrollY;
  820. }
  821. that.scrollTo(x, y, time || 400);
  822. },
  823. zoom: function (x, y, scale) {
  824. var that = this,
  825. relScale = scale / that.scale;
  826. x = x - that.wrapperOffsetLeft - that.x;
  827. y = y - that.wrapperOffsetTop - that.y;
  828. that.x = x - x * relScale + that.x;
  829. that.y = y - y * relScale + that.y;
  830. that.scale = scale;
  831. if (that.options.onZoomStart) that.options.onZoomStart.call(that);
  832. that.refresh();
  833. that._bind('webkitTransitionEnd');
  834. that._transitionTime(200);
  835. setTimeout(function () {
  836. that.zoomed = true;
  837. that.scroller.style.webkitTransform = trnOpen + that.x + 'px,' + that.y + 'px' + trnClose + ' scale(' + scale + ')';
  838. }, 0);
  839. }
  840. };
  841. var has3d = 'WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix(),
  842. hasTouch = 'ontouchstart' in window,
  843. hasGesture = 'ongesturestart' in window,
  844. // hasHashChange = 'onhashchange' in window,
  845. // hasTransitionEnd = 'onwebkittransitionend' in window,
  846. hasCompositing = 'WebKitTransitionEvent' in window,
  847. isIDevice = (/iphone|ipad/gi).test(navigator.appVersion),
  848. isAndroid = (/android/gi).test(navigator.appVersion),
  849. RESIZE_EV = 'onorientationchange' in window ? 'orientationchange' : 'resize',
  850. START_EV = hasTouch ? 'touchstart' : 'mousedown',
  851. MOVE_EV = hasTouch ? 'touchmove' : 'mousemove',
  852. END_EV = hasTouch ? 'touchend' : 'mouseup',
  853. CANCEL_EV = hasTouch ? 'touchcancel' : 'mouseup',
  854. trnOpen = 'translate' + (has3d ? '3d(' : '('),
  855. trnClose = has3d ? ',0)' : ')',
  856. m = Math;
  857. if (typeof exports !== 'undefined') exports.iScroll = iScroll;
  858. else window.iScroll = iScroll;
  859. })();