iscroll-lite.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944
  1. /*! iScroll v5.1.2 ~ (c) 2008-2014 Matteo Spinelli ~ http://cubiq.org/license */
  2. (function (window, document, Math) {
  3. var rAF = window.requestAnimationFrame ||
  4. window.webkitRequestAnimationFrame ||
  5. window.mozRequestAnimationFrame ||
  6. window.oRequestAnimationFrame ||
  7. window.msRequestAnimationFrame ||
  8. function (callback) { window.setTimeout(callback, 1000 / 60); };
  9. var utils = (function () {
  10. var me = {};
  11. var _elementStyle = document.createElement('div').style;
  12. var _vendor = (function () {
  13. var vendors = ['t', 'webkitT', 'MozT', 'msT', 'OT'],
  14. transform,
  15. i = 0,
  16. l = vendors.length;
  17. for ( ; i < l; i++ ) {
  18. transform = vendors[i] + 'ransform';
  19. if ( transform in _elementStyle ) return vendors[i].substr(0, vendors[i].length-1);
  20. }
  21. return false;
  22. })();
  23. function _prefixStyle (style) {
  24. if ( _vendor === false ) return false;
  25. if ( _vendor === '' ) return style;
  26. return _vendor + style.charAt(0).toUpperCase() + style.substr(1);
  27. }
  28. me.getTime = Date.now || function getTime () { return new Date().getTime(); };
  29. me.extend = function (target, obj) {
  30. for ( var i in obj ) {
  31. target[i] = obj[i];
  32. }
  33. };
  34. me.addEvent = function (el, type, fn, capture) {
  35. el.addEventListener(type, fn, !!capture);
  36. };
  37. me.removeEvent = function (el, type, fn, capture) {
  38. el.removeEventListener(type, fn, !!capture);
  39. };
  40. me.prefixPointerEvent = function (pointerEvent) {
  41. return window.MSPointerEvent ?
  42. 'MSPointer' + pointerEvent.charAt(9).toUpperCase() + pointerEvent.substr(10):
  43. pointerEvent;
  44. };
  45. me.momentum = function (current, start, time, lowerMargin, wrapperSize, deceleration) {
  46. var distance = current - start,
  47. speed = Math.abs(distance) / time,
  48. destination,
  49. duration;
  50. deceleration = deceleration === undefined ? 0.0006 : deceleration;
  51. destination = current + ( speed * speed ) / ( 2 * deceleration ) * ( distance < 0 ? -1 : 1 );
  52. duration = speed / deceleration;
  53. if ( destination < lowerMargin ) {
  54. destination = wrapperSize ? lowerMargin - ( wrapperSize / 2.5 * ( speed / 8 ) ) : lowerMargin;
  55. distance = Math.abs(destination - current);
  56. duration = distance / speed;
  57. } else if ( destination > 0 ) {
  58. destination = wrapperSize ? wrapperSize / 2.5 * ( speed / 8 ) : 0;
  59. distance = Math.abs(current) + destination;
  60. duration = distance / speed;
  61. }
  62. return {
  63. destination: Math.round(destination),
  64. duration: duration
  65. };
  66. };
  67. var _transform = _prefixStyle('transform');
  68. me.extend(me, {
  69. hasTransform: _transform !== false,
  70. hasPerspective: _prefixStyle('perspective') in _elementStyle,
  71. hasTouch: 'ontouchstart' in window,
  72. hasPointer: window.PointerEvent || window.MSPointerEvent, // IE10 is prefixed
  73. hasTransition: _prefixStyle('transition') in _elementStyle
  74. });
  75. // This should find all Android browsers lower than build 535.19 (both stock browser and webview)
  76. me.isBadAndroid = /Android /.test(window.navigator.appVersion) && !(/Chrome\/\d/.test(window.navigator.appVersion));
  77. me.extend(me.style = {}, {
  78. transform: _transform,
  79. transitionTimingFunction: _prefixStyle('transitionTimingFunction'),
  80. transitionDuration: _prefixStyle('transitionDuration'),
  81. transitionDelay: _prefixStyle('transitionDelay'),
  82. transformOrigin: _prefixStyle('transformOrigin')
  83. });
  84. me.hasClass = function (e, c) {
  85. var re = new RegExp("(^|\\s)" + c + "(\\s|$)");
  86. return re.test(e.className);
  87. };
  88. me.addClass = function (e, c) {
  89. if ( me.hasClass(e, c) ) {
  90. return;
  91. }
  92. var newclass = e.className.split(' ');
  93. newclass.push(c);
  94. e.className = newclass.join(' ');
  95. };
  96. me.removeClass = function (e, c) {
  97. if ( !me.hasClass(e, c) ) {
  98. return;
  99. }
  100. var re = new RegExp("(^|\\s)" + c + "(\\s|$)", 'g');
  101. e.className = e.className.replace(re, ' ');
  102. };
  103. me.offset = function (el) {
  104. var left = -el.offsetLeft,
  105. top = -el.offsetTop;
  106. // jshint -W084
  107. while (el = el.offsetParent) {
  108. left -= el.offsetLeft;
  109. top -= el.offsetTop;
  110. }
  111. // jshint +W084
  112. return {
  113. left: left,
  114. top: top
  115. };
  116. };
  117. me.preventDefaultException = function (el, exceptions) {
  118. for ( var i in exceptions ) {
  119. if ( exceptions[i].test(el[i]) ) {
  120. return true;
  121. }
  122. }
  123. return false;
  124. };
  125. me.extend(me.eventType = {}, {
  126. touchstart: 1,
  127. touchmove: 1,
  128. touchend: 1,
  129. mousedown: 2,
  130. mousemove: 2,
  131. mouseup: 2,
  132. pointerdown: 3,
  133. pointermove: 3,
  134. pointerup: 3,
  135. MSPointerDown: 3,
  136. MSPointerMove: 3,
  137. MSPointerUp: 3
  138. });
  139. me.extend(me.ease = {}, {
  140. quadratic: {
  141. style: 'cubic-bezier(0.25, 0.46, 0.45, 0.94)',
  142. fn: function (k) {
  143. return k * ( 2 - k );
  144. }
  145. },
  146. circular: {
  147. style: 'cubic-bezier(0.1, 0.57, 0.1, 1)', // Not properly "circular" but this looks better, it should be (0.075, 0.82, 0.165, 1)
  148. fn: function (k) {
  149. return Math.sqrt( 1 - ( --k * k ) );
  150. }
  151. },
  152. back: {
  153. style: 'cubic-bezier(0.175, 0.885, 0.32, 1.275)',
  154. fn: function (k) {
  155. var b = 4;
  156. return ( k = k - 1 ) * k * ( ( b + 1 ) * k + b ) + 1;
  157. }
  158. },
  159. bounce: {
  160. style: '',
  161. fn: function (k) {
  162. if ( ( k /= 1 ) < ( 1 / 2.75 ) ) {
  163. return 7.5625 * k * k;
  164. } else if ( k < ( 2 / 2.75 ) ) {
  165. return 7.5625 * ( k -= ( 1.5 / 2.75 ) ) * k + 0.75;
  166. } else if ( k < ( 2.5 / 2.75 ) ) {
  167. return 7.5625 * ( k -= ( 2.25 / 2.75 ) ) * k + 0.9375;
  168. } else {
  169. return 7.5625 * ( k -= ( 2.625 / 2.75 ) ) * k + 0.984375;
  170. }
  171. }
  172. },
  173. elastic: {
  174. style: '',
  175. fn: function (k) {
  176. var f = 0.22,
  177. e = 0.4;
  178. if ( k === 0 ) { return 0; }
  179. if ( k == 1 ) { return 1; }
  180. return ( e * Math.pow( 2, - 10 * k ) * Math.sin( ( k - f / 4 ) * ( 2 * Math.PI ) / f ) + 1 );
  181. }
  182. }
  183. });
  184. me.tap = function (e, eventName) {
  185. var ev = document.createEvent('Event');
  186. ev.initEvent(eventName, true, true);
  187. ev.pageX = e.pageX;
  188. ev.pageY = e.pageY;
  189. e.target.dispatchEvent(ev);
  190. };
  191. me.click = function (e) {
  192. var target = e.target,
  193. ev;
  194. if ( !(/(SELECT|INPUT|TEXTAREA)/i).test(target.tagName) ) {
  195. ev = document.createEvent('MouseEvents');
  196. ev.initMouseEvent('click', true, true, e.view, 1,
  197. target.screenX, target.screenY, target.clientX, target.clientY,
  198. e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
  199. 0, null);
  200. ev._constructed = true;
  201. target.dispatchEvent(ev);
  202. }
  203. };
  204. return me;
  205. })();
  206. function IScroll (el, options) {
  207. this.wrapper = typeof el == 'string' ? document.querySelector(el) : el;
  208. this.scroller = this.wrapper.children[0];
  209. this.scrollerStyle = this.scroller.style; // cache style for better performance
  210. this.options = {
  211. // INSERT POINT: OPTIONS
  212. startX: 0,
  213. startY: 0,
  214. scrollY: true,
  215. directionLockThreshold: 5,
  216. momentum: true,
  217. bounce: true,
  218. bounceTime: 600,
  219. bounceEasing: '',
  220. preventDefault: true,
  221. preventDefaultException: { tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT)$/ },
  222. HWCompositing: true,
  223. useTransition: true,
  224. useTransform: true
  225. };
  226. for ( var i in options ) {
  227. this.options[i] = options[i];
  228. }
  229. // Normalize options
  230. this.translateZ = this.options.HWCompositing && utils.hasPerspective ? ' translateZ(0)' : '';
  231. this.options.useTransition = utils.hasTransition && this.options.useTransition;
  232. this.options.useTransform = utils.hasTransform && this.options.useTransform;
  233. this.options.eventPassthrough = this.options.eventPassthrough === true ? 'vertical' : this.options.eventPassthrough;
  234. this.options.preventDefault = !this.options.eventPassthrough && this.options.preventDefault;
  235. // If you want eventPassthrough I have to lock one of the axes
  236. this.options.scrollY = this.options.eventPassthrough == 'vertical' ? false : this.options.scrollY;
  237. this.options.scrollX = this.options.eventPassthrough == 'horizontal' ? false : this.options.scrollX;
  238. // With eventPassthrough we also need lockDirection mechanism
  239. this.options.freeScroll = this.options.freeScroll && !this.options.eventPassthrough;
  240. this.options.directionLockThreshold = this.options.eventPassthrough ? 0 : this.options.directionLockThreshold;
  241. this.options.bounceEasing = typeof this.options.bounceEasing == 'string' ? utils.ease[this.options.bounceEasing] || utils.ease.circular : this.options.bounceEasing;
  242. this.options.resizePolling = this.options.resizePolling === undefined ? 60 : this.options.resizePolling;
  243. if ( this.options.tap === true ) {
  244. this.options.tap = 'tap';
  245. }
  246. // INSERT POINT: NORMALIZATION
  247. // Some defaults
  248. this.x = 0;
  249. this.y = 0;
  250. this.directionX = 0;
  251. this.directionY = 0;
  252. this._events = {};
  253. // INSERT POINT: DEFAULTS
  254. this._init();
  255. this.refresh();
  256. this.scrollTo(this.options.startX, this.options.startY);
  257. this.enable();
  258. }
  259. IScroll.prototype = {
  260. version: '5.1.2',
  261. _init: function () {
  262. this._initEvents();
  263. // INSERT POINT: _init
  264. },
  265. destroy: function () {
  266. this._initEvents(true);
  267. this._execEvent('destroy');
  268. },
  269. _transitionEnd: function (e) {
  270. if ( e.target != this.scroller || !this.isInTransition ) {
  271. return;
  272. }
  273. this._transitionTime();
  274. if ( !this.resetPosition(this.options.bounceTime) ) {
  275. this.isInTransition = false;
  276. this._execEvent('scrollEnd');
  277. }
  278. },
  279. _start: function (e) {
  280. // React to left mouse button only
  281. if ( utils.eventType[e.type] != 1 ) {
  282. if ( e.button !== 0 ) {
  283. return;
  284. }
  285. }
  286. if ( !this.enabled || (this.initiated && utils.eventType[e.type] !== this.initiated) ) {
  287. return;
  288. }
  289. if ( this.options.preventDefault && !utils.isBadAndroid && !utils.preventDefaultException(e.target, this.options.preventDefaultException) ) {
  290. e.preventDefault();
  291. }
  292. var point = e.touches ? e.touches[0] : e,
  293. pos;
  294. this.initiated = utils.eventType[e.type];
  295. this.moved = false;
  296. this.distX = 0;
  297. this.distY = 0;
  298. this.directionX = 0;
  299. this.directionY = 0;
  300. this.directionLocked = 0;
  301. this._transitionTime();
  302. this.startTime = utils.getTime();
  303. if ( this.options.useTransition && this.isInTransition ) {
  304. this.isInTransition = false;
  305. pos = this.getComputedPosition();
  306. this._translate(Math.round(pos.x), Math.round(pos.y));
  307. this._execEvent('scrollEnd');
  308. } else if ( !this.options.useTransition && this.isAnimating ) {
  309. this.isAnimating = false;
  310. this._execEvent('scrollEnd');
  311. }
  312. this.startX = this.x;
  313. this.startY = this.y;
  314. this.absStartX = this.x;
  315. this.absStartY = this.y;
  316. this.pointX = point.pageX;
  317. this.pointY = point.pageY;
  318. this._execEvent('beforeScrollStart');
  319. },
  320. _move: function (e) {
  321. if ( !this.enabled || utils.eventType[e.type] !== this.initiated ) {
  322. return;
  323. }
  324. if ( this.options.preventDefault ) { // increases performance on Android? TODO: check!
  325. e.preventDefault();
  326. }
  327. var point = e.touches ? e.touches[0] : e,
  328. deltaX = point.pageX - this.pointX,
  329. deltaY = point.pageY - this.pointY,
  330. timestamp = utils.getTime(),
  331. newX, newY,
  332. absDistX, absDistY;
  333. this.pointX = point.pageX;
  334. this.pointY = point.pageY;
  335. this.distX += deltaX;
  336. this.distY += deltaY;
  337. absDistX = Math.abs(this.distX);
  338. absDistY = Math.abs(this.distY);
  339. // We need to move at least 10 pixels for the scrolling to initiate
  340. if ( timestamp - this.endTime > 300 && (absDistX < 10 && absDistY < 10) ) {
  341. return;
  342. }
  343. // If you are scrolling in one direction lock the other
  344. if ( !this.directionLocked && !this.options.freeScroll ) {
  345. if ( absDistX > absDistY + this.options.directionLockThreshold ) {
  346. this.directionLocked = 'h'; // lock horizontally
  347. } else if ( absDistY >= absDistX + this.options.directionLockThreshold ) {
  348. this.directionLocked = 'v'; // lock vertically
  349. } else {
  350. this.directionLocked = 'n'; // no lock
  351. }
  352. }
  353. if ( this.directionLocked == 'h' ) {
  354. if ( this.options.eventPassthrough == 'vertical' ) {
  355. e.preventDefault();
  356. } else if ( this.options.eventPassthrough == 'horizontal' ) {
  357. this.initiated = false;
  358. return;
  359. }
  360. deltaY = 0;
  361. } else if ( this.directionLocked == 'v' ) {
  362. if ( this.options.eventPassthrough == 'horizontal' ) {
  363. e.preventDefault();
  364. } else if ( this.options.eventPassthrough == 'vertical' ) {
  365. this.initiated = false;
  366. return;
  367. }
  368. deltaX = 0;
  369. }
  370. deltaX = this.hasHorizontalScroll ? deltaX : 0;
  371. deltaY = this.hasVerticalScroll ? deltaY : 0;
  372. newX = this.x + deltaX;
  373. newY = this.y + deltaY;
  374. // Slow down if outside of the boundaries
  375. if ( newX > 0 || newX < this.maxScrollX ) {
  376. newX = this.options.bounce ? this.x + deltaX / 3 : newX > 0 ? 0 : this.maxScrollX;
  377. }
  378. if ( newY > 0 || newY < this.maxScrollY ) {
  379. newY = this.options.bounce ? this.y + deltaY / 3 : newY > 0 ? 0 : this.maxScrollY;
  380. }
  381. this.directionX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0;
  382. this.directionY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0;
  383. if ( !this.moved ) {
  384. this._execEvent('scrollStart');
  385. }
  386. this.moved = true;
  387. this._translate(newX, newY);
  388. /* REPLACE START: _move */
  389. if ( timestamp - this.startTime > 300 ) {
  390. this.startTime = timestamp;
  391. this.startX = this.x;
  392. this.startY = this.y;
  393. }
  394. /* REPLACE END: _move */
  395. },
  396. _end: function (e) {
  397. if ( !this.enabled || utils.eventType[e.type] !== this.initiated ) {
  398. return;
  399. }
  400. if ( this.options.preventDefault && !utils.preventDefaultException(e.target, this.options.preventDefaultException) ) {
  401. e.preventDefault();
  402. }
  403. var point = e.changedTouches ? e.changedTouches[0] : e,
  404. momentumX,
  405. momentumY,
  406. duration = utils.getTime() - this.startTime,
  407. newX = Math.round(this.x),
  408. newY = Math.round(this.y),
  409. distanceX = Math.abs(newX - this.startX),
  410. distanceY = Math.abs(newY - this.startY),
  411. time = 0,
  412. easing = '';
  413. this.isInTransition = 0;
  414. this.initiated = 0;
  415. this.endTime = utils.getTime();
  416. // reset if we are outside of the boundaries
  417. if ( this.resetPosition(this.options.bounceTime) ) {
  418. return;
  419. }
  420. this.scrollTo(newX, newY); // ensures that the last position is rounded
  421. // we scrolled less than 10 pixels
  422. if ( !this.moved ) {
  423. if ( this.options.tap ) {
  424. utils.tap(e, this.options.tap);
  425. }
  426. if ( this.options.click ) {
  427. utils.click(e);
  428. }
  429. this._execEvent('scrollCancel');
  430. return;
  431. }
  432. if ( this._events.flick && duration < 200 && distanceX < 100 && distanceY < 100 ) {
  433. this._execEvent('flick');
  434. return;
  435. }
  436. // start momentum animation if needed
  437. if ( this.options.momentum && duration < 300 ) {
  438. momentumX = this.hasHorizontalScroll ? utils.momentum(this.x, this.startX, duration, this.maxScrollX, this.options.bounce ? this.wrapperWidth : 0, this.options.deceleration) : { destination: newX, duration: 0 };
  439. momentumY = this.hasVerticalScroll ? utils.momentum(this.y, this.startY, duration, this.maxScrollY, this.options.bounce ? this.wrapperHeight : 0, this.options.deceleration) : { destination: newY, duration: 0 };
  440. newX = momentumX.destination;
  441. newY = momentumY.destination;
  442. time = Math.max(momentumX.duration, momentumY.duration);
  443. this.isInTransition = 1;
  444. }
  445. // INSERT POINT: _end
  446. if ( newX != this.x || newY != this.y ) {
  447. // change easing function when scroller goes out of the boundaries
  448. if ( newX > 0 || newX < this.maxScrollX || newY > 0 || newY < this.maxScrollY ) {
  449. easing = utils.ease.quadratic;
  450. }
  451. this.scrollTo(newX, newY, time, easing);
  452. return;
  453. }
  454. this._execEvent('scrollEnd');
  455. },
  456. _resize: function () {
  457. var that = this;
  458. clearTimeout(this.resizeTimeout);
  459. this.resizeTimeout = setTimeout(function () {
  460. that.refresh();
  461. }, this.options.resizePolling);
  462. },
  463. resetPosition: function (time) {
  464. var x = this.x,
  465. y = this.y;
  466. time = time || 0;
  467. if ( !this.hasHorizontalScroll || this.x > 0 ) {
  468. x = 0;
  469. } else if ( this.x < this.maxScrollX ) {
  470. x = this.maxScrollX;
  471. }
  472. if ( !this.hasVerticalScroll || this.y > 0 ) {
  473. y = 0;
  474. } else if ( this.y < this.maxScrollY ) {
  475. y = this.maxScrollY;
  476. }
  477. if ( x == this.x && y == this.y ) {
  478. return false;
  479. }
  480. this.scrollTo(x, y, time, this.options.bounceEasing);
  481. return true;
  482. },
  483. disable: function () {
  484. this.enabled = false;
  485. },
  486. enable: function () {
  487. this.enabled = true;
  488. },
  489. refresh: function () {
  490. var rf = this.wrapper.offsetHeight; // Force reflow
  491. this.wrapperWidth = this.wrapper.clientWidth;
  492. this.wrapperHeight = this.wrapper.clientHeight;
  493. /* REPLACE START: refresh */
  494. this.scrollerWidth = this.scroller.offsetWidth;
  495. this.scrollerHeight = this.scroller.offsetHeight;
  496. this.maxScrollX = this.wrapperWidth - this.scrollerWidth;
  497. this.maxScrollY = this.wrapperHeight - this.scrollerHeight;
  498. /* REPLACE END: refresh */
  499. this.hasHorizontalScroll = this.options.scrollX && this.maxScrollX < 0;
  500. this.hasVerticalScroll = this.options.scrollY && this.maxScrollY < 0;
  501. if ( !this.hasHorizontalScroll ) {
  502. this.maxScrollX = 0;
  503. this.scrollerWidth = this.wrapperWidth;
  504. }
  505. if ( !this.hasVerticalScroll ) {
  506. this.maxScrollY = 0;
  507. this.scrollerHeight = this.wrapperHeight;
  508. }
  509. this.endTime = 0;
  510. this.directionX = 0;
  511. this.directionY = 0;
  512. this.wrapperOffset = utils.offset(this.wrapper);
  513. this._execEvent('refresh');
  514. this.resetPosition();
  515. // INSERT POINT: _refresh
  516. },
  517. on: function (type, fn) {
  518. if ( !this._events[type] ) {
  519. this._events[type] = [];
  520. }
  521. this._events[type].push(fn);
  522. },
  523. off: function (type, fn) {
  524. if ( !this._events[type] ) {
  525. return;
  526. }
  527. var index = this._events[type].indexOf(fn);
  528. if ( index > -1 ) {
  529. this._events[type].splice(index, 1);
  530. }
  531. },
  532. _execEvent: function (type) {
  533. if ( !this._events[type] ) {
  534. return;
  535. }
  536. var i = 0,
  537. l = this._events[type].length;
  538. if ( !l ) {
  539. return;
  540. }
  541. for ( ; i < l; i++ ) {
  542. this._events[type][i].apply(this, [].slice.call(arguments, 1));
  543. }
  544. },
  545. scrollBy: function (x, y, time, easing) {
  546. x = this.x + x;
  547. y = this.y + y;
  548. time = time || 0;
  549. this.scrollTo(x, y, time, easing);
  550. },
  551. scrollTo: function (x, y, time, easing) {
  552. easing = easing || utils.ease.circular;
  553. this.isInTransition = this.options.useTransition && time > 0;
  554. if ( !time || (this.options.useTransition && easing.style) ) {
  555. this._transitionTimingFunction(easing.style);
  556. this._transitionTime(time);
  557. this._translate(x, y);
  558. } else {
  559. this._animate(x, y, time, easing.fn);
  560. }
  561. },
  562. scrollToElement: function (el, time, offsetX, offsetY, easing) {
  563. el = el.nodeType ? el : this.scroller.querySelector(el);
  564. if ( !el ) {
  565. return;
  566. }
  567. var pos = utils.offset(el);
  568. pos.left -= this.wrapperOffset.left;
  569. pos.top -= this.wrapperOffset.top;
  570. // if offsetX/Y are true we center the element to the screen
  571. if ( offsetX === true ) {
  572. offsetX = Math.round(el.offsetWidth / 2 - this.wrapper.offsetWidth / 2);
  573. }
  574. if ( offsetY === true ) {
  575. offsetY = Math.round(el.offsetHeight / 2 - this.wrapper.offsetHeight / 2);
  576. }
  577. pos.left -= offsetX || 0;
  578. pos.top -= offsetY || 0;
  579. pos.left = pos.left > 0 ? 0 : pos.left < this.maxScrollX ? this.maxScrollX : pos.left;
  580. pos.top = pos.top > 0 ? 0 : pos.top < this.maxScrollY ? this.maxScrollY : pos.top;
  581. time = time === undefined || time === null || time === 'auto' ? Math.max(Math.abs(this.x-pos.left), Math.abs(this.y-pos.top)) : time;
  582. this.scrollTo(pos.left, pos.top, time, easing);
  583. },
  584. _transitionTime: function (time) {
  585. time = time || 0;
  586. this.scrollerStyle[utils.style.transitionDuration] = time + 'ms';
  587. if ( !time && utils.isBadAndroid ) {
  588. this.scrollerStyle[utils.style.transitionDuration] = '0.001s';
  589. }
  590. // INSERT POINT: _transitionTime
  591. },
  592. _transitionTimingFunction: function (easing) {
  593. this.scrollerStyle[utils.style.transitionTimingFunction] = easing;
  594. // INSERT POINT: _transitionTimingFunction
  595. },
  596. _translate: function (x, y) {
  597. if ( this.options.useTransform ) {
  598. /* REPLACE START: _translate */
  599. this.scrollerStyle[utils.style.transform] = 'translate(' + x + 'px,' + y + 'px)' + this.translateZ;
  600. /* REPLACE END: _translate */
  601. } else {
  602. x = Math.round(x);
  603. y = Math.round(y);
  604. this.scrollerStyle.left = x + 'px';
  605. this.scrollerStyle.top = y + 'px';
  606. }
  607. this.x = x;
  608. this.y = y;
  609. // INSERT POINT: _translate
  610. },
  611. _initEvents: function (remove) {
  612. var eventType = remove ? utils.removeEvent : utils.addEvent,
  613. target = this.options.bindToWrapper ? this.wrapper : window;
  614. eventType(window, 'orientationchange', this);
  615. eventType(window, 'resize', this);
  616. if ( this.options.click ) {
  617. eventType(this.wrapper, 'click', this, true);
  618. }
  619. if ( !this.options.disableMouse ) {
  620. eventType(this.wrapper, 'mousedown', this);
  621. eventType(target, 'mousemove', this);
  622. eventType(target, 'mousecancel', this);
  623. eventType(target, 'mouseup', this);
  624. }
  625. if ( utils.hasPointer && !this.options.disablePointer ) {
  626. eventType(this.wrapper, utils.prefixPointerEvent('pointerdown'), this);
  627. eventType(target, utils.prefixPointerEvent('pointermove'), this);
  628. eventType(target, utils.prefixPointerEvent('pointercancel'), this);
  629. eventType(target, utils.prefixPointerEvent('pointerup'), this);
  630. }
  631. if ( utils.hasTouch && !this.options.disableTouch ) {
  632. eventType(this.wrapper, 'touchstart', this);
  633. eventType(target, 'touchmove', this);
  634. eventType(target, 'touchcancel', this);
  635. eventType(target, 'touchend', this);
  636. }
  637. eventType(this.scroller, 'transitionend', this);
  638. eventType(this.scroller, 'webkitTransitionEnd', this);
  639. eventType(this.scroller, 'oTransitionEnd', this);
  640. eventType(this.scroller, 'MSTransitionEnd', this);
  641. },
  642. getComputedPosition: function () {
  643. var matrix = window.getComputedStyle(this.scroller, null),
  644. x, y;
  645. if ( this.options.useTransform ) {
  646. matrix = matrix[utils.style.transform].split(')')[0].split(', ');
  647. x = +(matrix[12] || matrix[4]);
  648. y = +(matrix[13] || matrix[5]);
  649. } else {
  650. x = +matrix.left.replace(/[^-\d.]/g, '');
  651. y = +matrix.top.replace(/[^-\d.]/g, '');
  652. }
  653. return { x: x, y: y };
  654. },
  655. _animate: function (destX, destY, duration, easingFn) {
  656. var that = this,
  657. startX = this.x,
  658. startY = this.y,
  659. startTime = utils.getTime(),
  660. destTime = startTime + duration;
  661. function step () {
  662. var now = utils.getTime(),
  663. newX, newY,
  664. easing;
  665. if ( now >= destTime ) {
  666. that.isAnimating = false;
  667. that._translate(destX, destY);
  668. if ( !that.resetPosition(that.options.bounceTime) ) {
  669. that._execEvent('scrollEnd');
  670. }
  671. return;
  672. }
  673. now = ( now - startTime ) / duration;
  674. easing = easingFn(now);
  675. newX = ( destX - startX ) * easing + startX;
  676. newY = ( destY - startY ) * easing + startY;
  677. that._translate(newX, newY);
  678. if ( that.isAnimating ) {
  679. rAF(step);
  680. }
  681. }
  682. this.isAnimating = true;
  683. step();
  684. },
  685. handleEvent: function (e) {
  686. switch ( e.type ) {
  687. case 'touchstart':
  688. case 'pointerdown':
  689. case 'MSPointerDown':
  690. case 'mousedown':
  691. this._start(e);
  692. break;
  693. case 'touchmove':
  694. case 'pointermove':
  695. case 'MSPointerMove':
  696. case 'mousemove':
  697. this._move(e);
  698. break;
  699. case 'touchend':
  700. case 'pointerup':
  701. case 'MSPointerUp':
  702. case 'mouseup':
  703. case 'touchcancel':
  704. case 'pointercancel':
  705. case 'MSPointerCancel':
  706. case 'mousecancel':
  707. this._end(e);
  708. break;
  709. case 'orientationchange':
  710. case 'resize':
  711. this._resize();
  712. break;
  713. case 'transitionend':
  714. case 'webkitTransitionEnd':
  715. case 'oTransitionEnd':
  716. case 'MSTransitionEnd':
  717. this._transitionEnd(e);
  718. break;
  719. case 'wheel':
  720. case 'DOMMouseScroll':
  721. case 'mousewheel':
  722. this._wheel(e);
  723. break;
  724. case 'keydown':
  725. this._key(e);
  726. break;
  727. case 'click':
  728. if ( !e._constructed ) {
  729. e.preventDefault();
  730. e.stopPropagation();
  731. }
  732. break;
  733. }
  734. }
  735. };
  736. IScroll.utils = utils;
  737. if ( typeof module != 'undefined' && module.exports ) {
  738. module.exports = IScroll;
  739. } else {
  740. window.IScroll = IScroll;
  741. }
  742. })(window, document, Math);