2 * iScroll v4.1.4 ~ Copyright (c) 2011 Matteo Spinelli, http://cubiq.org
3 * Released under MIT license, http://cubiq.org/license
8 vendor
= (/webkit/i).test(navigator
.appVersion
) ? 'webkit' :
9 (/firefox/i).test(navigator
.userAgent
) ? 'Moz' :
10 'opera' in window
? 'O' : '',
12 // Browser capabilities
13 has3d
= 'WebKitCSSMatrix' in window
&& 'm11' in new WebKitCSSMatrix(),
14 hasTouch
= 'ontouchstart' in window
,
15 hasTransform
= vendor
+ 'Transform' in document
.documentElement
.style
,
16 isAndroid
= (/android/gi).test(navigator
.appVersion
),
17 isIDevice
= (/iphone|ipad/gi).test(navigator
.appVersion
),
18 isPlaybook
= (/playbook/gi).test(navigator
.appVersion
),
19 hasTransitionEnd
= (isIDevice
|| isPlaybook
) && 'onwebkittransitionend' in window
,
20 nextFrame
= (function() {
21 return window
.requestAnimationFrame
22 || window
.webkitRequestAnimationFrame
23 || window
.mozRequestAnimationFrame
24 || window
.oRequestAnimationFrame
25 || window
.msRequestAnimationFrame
26 || function(callback
) { return setTimeout(callback
, 17); }
28 cancelFrame
= (function () {
29 return window
.cancelRequestAnimationFrame
30 || window
.webkitCancelRequestAnimationFrame
31 || window
.mozCancelRequestAnimationFrame
32 || window
.oCancelRequestAnimationFrame
33 || window
.msCancelRequestAnimationFrame
38 RESIZE_EV
= 'onorientationchange' in window
? 'orientationchange' : 'resize',
39 START_EV
= hasTouch
? 'touchstart' : 'mousedown',
40 MOVE_EV
= hasTouch
? 'touchmove' : 'mousemove',
41 END_EV
= hasTouch
? 'touchend' : 'mouseup',
42 CANCEL_EV
= hasTouch
? 'touchcancel' : 'mouseup',
43 WHEEL_EV
= vendor
== 'Moz' ? 'DOMMouseScroll' : 'mousewheel',
46 trnOpen
= 'translate' + (has3d
? '3d(' : '('),
47 trnClose
= has3d
? ',0)' : ')',
50 iScroll = function (el
, options
) {
55 that
.wrapper
= typeof el
== 'object' ? el
: doc
.getElementById(el
);
56 that
.wrapper
.style
.overflow
= 'hidden';
57 that
.scroller
= that
.wrapper
.children
[0];
73 fixedScrollbar
: isAndroid
,
74 hideScrollbar
: isIDevice
,
75 fadeScrollbar
: isIDevice
&& has3d
,
90 onBeforeScrollStart: function (e
) { e
.preventDefault(); },
92 onBeforeScrollMove
: null,
94 onBeforeScrollEnd
: null,
100 // User defined options
101 for (i
in options
) that
.options
[i
] = options
[i
];
104 that
.options
.useTransform
= hasTransform
? that
.options
.useTransform
: false;
105 that
.options
.hScrollbar
= that
.options
.hScroll
&& that
.options
.hScrollbar
;
106 that
.options
.vScrollbar
= that
.options
.vScroll
&& that
.options
.vScrollbar
;
107 that
.options
.zoom
= that
.options
.useTransform
&& that
.options
.zoom
;
108 that
.options
.useTransition
= hasTransitionEnd
&& that
.options
.useTransition
;
110 // Set some default styles
111 that
.scroller
.style
[vendor
+ 'TransitionProperty'] = that
.options
.useTransform
? '-' + vendor
.toLowerCase() + '-transform' : 'top left';
112 that
.scroller
.style
[vendor
+ 'TransitionDuration'] = '0';
113 that
.scroller
.style
[vendor
+ 'TransformOrigin'] = '0 0';
114 if (that
.options
.useTransition
) that
.scroller
.style
[vendor
+ 'TransitionTimingFunction'] = 'cubic-bezier(0.33,0.66,0.66,1)';
116 if (that
.options
.useTransform
) that
.scroller
.style
[vendor
+ 'Transform'] = trnOpen
+ '0,0' + trnClose
;
117 else that
.scroller
.style
.cssText
+= ';top:0;left:0';
119 if (that
.options
.useTransition
) that
.options
.fixedScrollbar
= true;
123 that
._bind(RESIZE_EV
, window
);
124 that
._bind(START_EV
);
126 that
._bind('mouseout', that
.wrapper
);
127 that
._bind(WHEEL_EV
);
132 iScroll
.prototype = {
138 currPageX
: 0, currPageY
: 0,
139 pagesX
: [], pagesY
: [],
142 handleEvent: function (e
) {
145 case START_EV
: that
._start(e
); break;
146 case MOVE_EV
: that
._move(e
); break;
148 case CANCEL_EV
: that
._end(e
); break;
149 case RESIZE_EV
: that
._resize(); break;
150 case WHEEL_EV
: that
._wheel(e
); break;
151 case 'mouseout': that
._mouseout(e
); break;
152 case 'webkitTransitionEnd': that
._transitionEnd(e
); break;
156 _scrollbar: function (dir
) {
161 if (!that
[dir
+ 'Scrollbar']) {
162 if (that
[dir
+ 'ScrollbarWrapper']) {
163 if (hasTransform
) that
[dir
+ 'ScrollbarIndicator'].style
[vendor
+ 'Transform'] = '';
164 that
[dir
+ 'ScrollbarWrapper'].parentNode
.removeChild(that
[dir
+ 'ScrollbarWrapper']);
165 that
[dir
+ 'ScrollbarWrapper'] = null;
166 that
[dir
+ 'ScrollbarIndicator'] = null;
172 if (!that
[dir
+ 'ScrollbarWrapper']) {
173 // Create the scrollbar wrapper
174 bar
= doc
.createElement('div');
176 if (that
.options
.scrollbarClass
) bar
.className
= that
.options
.scrollbarClass
+ dir
.toUpperCase();
177 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');
179 bar
.style
.cssText
+= ';pointer-events:none;-' + vendor
+ '-transition-property:opacity;-' + vendor
+ '-transition-duration:' + (that
.options
.fadeScrollbar
? '350ms' : '0') + ';overflow:hidden;opacity:' + (that
.options
.hideScrollbar
? '0' : '1');
181 that
.wrapper
.appendChild(bar
);
182 that
[dir
+ 'ScrollbarWrapper'] = bar
;
184 // Create the scrollbar indicator
185 bar
= doc
.createElement('div');
186 if (!that
.options
.scrollbarClass
) {
187 bar
.style
.cssText
= 'position:absolute;z-index:100;background:rgba(0,0,0,0.5);border:1px solid rgba(255,255,255,0.9);-' + vendor
+ '-background-clip:padding-box;-' + vendor
+ '-box-sizing:border-box;' + (dir
== 'h' ? 'height:100%' : 'width:100%') + ';-' + vendor
+ '-border-radius:3px;border-radius:3px';
189 bar
.style
.cssText
+= ';pointer-events:none;-' + vendor
+ '-transition-property:-' + vendor
+ '-transform;-' + vendor
+ '-transition-timing-function:cubic-bezier(0.33,0.66,0.66,1);-' + vendor
+ '-transition-duration:0;-' + vendor
+ '-transform:' + trnOpen
+ '0,0' + trnClose
;
190 if (that
.options
.useTransition
) bar
.style
.cssText
+= ';-' + vendor
+ '-transition-timing-function:cubic-bezier(0.33,0.66,0.66,1)';
192 that
[dir
+ 'ScrollbarWrapper'].appendChild(bar
);
193 that
[dir
+ 'ScrollbarIndicator'] = bar
;
197 that
.hScrollbarSize
= that
.hScrollbarWrapper
.clientWidth
;
198 that
.hScrollbarIndicatorSize
= m
.max(m
.round(that
.hScrollbarSize
* that
.hScrollbarSize
/ that
.scrollerW
), 8);
199 that
.hScrollbarIndicator
.style
.width
= that
.hScrollbarIndicatorSize
+ 'px';
200 that
.hScrollbarMaxScroll
= that
.hScrollbarSize
- that
.hScrollbarIndicatorSize
;
201 that
.hScrollbarProp
= that
.hScrollbarMaxScroll
/ that
.maxScrollX
;
203 that
.vScrollbarSize
= that
.vScrollbarWrapper
.clientHeight
;
204 that
.vScrollbarIndicatorSize
= m
.max(m
.round(that
.vScrollbarSize
* that
.vScrollbarSize
/ that
.scrollerH
), 8);
205 that
.vScrollbarIndicator
.style
.height
= that
.vScrollbarIndicatorSize
+ 'px';
206 that
.vScrollbarMaxScroll
= that
.vScrollbarSize
- that
.vScrollbarIndicatorSize
;
207 that
.vScrollbarProp
= that
.vScrollbarMaxScroll
/ that
.maxScrollY
;
211 that
._scrollbarPos(dir
, true);
214 _resize: function () {
218 _pos: function (x
, y
) {
219 x
= this.hScroll
? x
: 0;
220 y
= this.vScroll
? y
: 0;
222 if (this.options
.useTransform
) {
223 this.scroller
.style
[vendor
+ 'Transform'] = trnOpen
+ x
+ 'px,' + y
+ 'px' + trnClose
+ ' scale(' + this.scale
+ ')';
227 this.scroller
.style
.left
= x
+ 'px';
228 this.scroller
.style
.top
= y
+ 'px';
234 this._scrollbarPos('h');
235 this._scrollbarPos('v');
238 _scrollbarPos: function (dir
, hidden
) {
240 pos
= dir
== 'h' ? that
.x
: that
.y
,
243 if (!that
[dir
+ 'Scrollbar']) return;
245 pos
= that
[dir
+ 'ScrollbarProp'] * pos
;
248 if (!that
.options
.fixedScrollbar
) {
249 size
= that
[dir
+ 'ScrollbarIndicatorSize'] + m
.round(pos
* 3);
250 if (size
< 8) size
= 8;
251 that
[dir
+ 'ScrollbarIndicator'].style
[dir
== 'h' ? 'width' : 'height'] = size
+ 'px';
254 } else if (pos
> that
[dir
+ 'ScrollbarMaxScroll']) {
255 if (!that
.options
.fixedScrollbar
) {
256 size
= that
[dir
+ 'ScrollbarIndicatorSize'] - m
.round((pos
- that
[dir
+ 'ScrollbarMaxScroll']) * 3);
257 if (size
< 8) size
= 8;
258 that
[dir
+ 'ScrollbarIndicator'].style
[dir
== 'h' ? 'width' : 'height'] = size
+ 'px';
259 pos
= that
[dir
+ 'ScrollbarMaxScroll'] + (that
[dir
+ 'ScrollbarIndicatorSize'] - size
);
261 pos
= that
[dir
+ 'ScrollbarMaxScroll'];
265 that
[dir
+ 'ScrollbarWrapper'].style
[vendor
+ 'TransitionDelay'] = '0';
266 that
[dir
+ 'ScrollbarWrapper'].style
.opacity
= hidden
&& that
.options
.hideScrollbar
? '0' : '1';
267 that
[dir
+ 'ScrollbarIndicator'].style
[vendor
+ 'Transform'] = trnOpen
+ (dir
== 'h' ? pos
+ 'px,0' : '0,' + pos
+ 'px') + trnClose
;
270 _start: function (e
) {
272 point
= hasTouch
? e
.touches
[0] : e
,
276 if (!that
.enabled
) return;
278 if (that
.options
.onBeforeScrollStart
) that
.options
.onBeforeScrollStart
.call(that
, e
);
280 if (that
.options
.useTransition
) that
._transitionTime(0);
283 that
.animating
= false;
293 if (that
.options
.zoom
&& hasTouch
&& e
.touches
.length
> 1) {
294 c1
= m
.abs(e
.touches
[0].pageX
-e
.touches
[1].pageX
);
295 c2
= m
.abs(e
.touches
[0].pageY
-e
.touches
[1].pageY
);
296 that
.touchesDistStart
= m
.sqrt(c1
* c1
+ c2
* c2
);
298 that
.originX
= m
.abs(e
.touches
[0].pageX
+ e
.touches
[1].pageX
- that
.wrapperOffsetLeft
* 2) / 2 - that
.x
;
299 that
.originY
= m
.abs(e
.touches
[0].pageY
+ e
.touches
[1].pageY
- that
.wrapperOffsetTop
* 2) / 2 - that
.y
;
302 if (that
.options
.momentum
) {
303 if (that
.options
.useTransform
) {
304 // Very lame general purpose alternative to CSSMatrix
305 matrix
= getComputedStyle(that
.scroller
, null)[vendor
+ 'Transform'].replace(/[^0-9-.,]/g, '').split(',');
309 x
= getComputedStyle(that
.scroller
, null).left
.replace(/[^0-9-]/g, '') * 1;
310 y
= getComputedStyle(that
.scroller
, null).top
.replace(/[^0-9-]/g, '') * 1;
313 if (x
!= that
.x
|| y
!= that
.y
) {
314 if (that
.options
.useTransition
) that
._unbind('webkitTransitionEnd');
315 else cancelFrame(that
.aniTime
);
321 that
.absStartX
= that
.x
; // Needed by snap threshold
322 that
.absStartY
= that
.y
;
324 that
.startX
= that
.x
;
325 that
.startY
= that
.y
;
326 that
.pointX
= point
.pageX
;
327 that
.pointY
= point
.pageY
;
329 that
.startTime
= e
.timeStamp
|| (new Date()).getTime();
331 if (that
.options
.onScrollStart
) that
.options
.onScrollStart
.call(that
, e
);
335 that
._bind(CANCEL_EV
);
338 _move: function (e
) {
340 point
= hasTouch
? e
.touches
[0] : e
,
341 deltaX
= point
.pageX
- that
.pointX
,
342 deltaY
= point
.pageY
- that
.pointY
,
343 newX
= that
.x
+ deltaX
,
344 newY
= that
.y
+ deltaY
,
346 timestamp
= e
.timeStamp
|| (new Date()).getTime();
348 if (that
.options
.onBeforeScrollMove
) that
.options
.onBeforeScrollMove
.call(that
, e
);
351 if (that
.options
.zoom
&& hasTouch
&& e
.touches
.length
> 1) {
352 c1
= m
.abs(e
.touches
[0].pageX
- e
.touches
[1].pageX
);
353 c2
= m
.abs(e
.touches
[0].pageY
- e
.touches
[1].pageY
);
354 that
.touchesDist
= m
.sqrt(c1
*c1
+c2
*c2
);
358 scale
= 1 / that
.touchesDistStart
* that
.touchesDist
* this.scale
;
359 if (scale
< 0.5) scale
= 0.5;
360 else if (scale
> that
.options
.zoomMax
) scale
= that
.options
.zoomMax
;
361 that
.lastScale
= scale
/ this.scale
;
363 newX
= this.originX
- this.originX
* that
.lastScale
+ this.x
,
364 newY
= this.originY
- this.originY
* that
.lastScale
+ this.y
;
366 this.scroller
.style
[vendor
+ 'Transform'] = trnOpen
+ newX
+ 'px,' + newY
+ 'px' + trnClose
+ ' scale(' + scale
+ ')';
371 that
.pointX
= point
.pageX
;
372 that
.pointY
= point
.pageY
;
374 // Slow down if outside of the boundaries
375 if (newX
> 0 || newX
< that
.maxScrollX
) {
376 newX
= that
.options
.bounce
? that
.x
+ (deltaX
/ 2) : newX
>= 0 || that
.maxScrollX
>= 0 ? 0 : that
.maxScrollX
;
378 if (newY
> 0 || newY
< that
.maxScrollY
) {
379 newY
= that
.options
.bounce
? that
.y
+ (deltaY
/ 2) : newY
>= 0 || that
.maxScrollY
>= 0 ? 0 : that
.maxScrollY
;
382 if (that
.absDistX
< 6 && that
.absDistY
< 6) {
383 that
.distX
+= deltaX
;
384 that
.distY
+= deltaY
;
385 that
.absDistX
= m
.abs(that
.distX
);
386 that
.absDistY
= m
.abs(that
.distY
);
392 if (that
.options
.lockDirection
) {
393 if (that
.absDistX
> that
.absDistY
+ 5) {
396 } else if (that
.absDistY
> that
.absDistX
+ 5) {
403 that
._pos(newX
, newY
);
404 that
.dirX
= deltaX
> 0 ? -1 : deltaX
< 0 ? 1 : 0;
405 that
.dirY
= deltaY
> 0 ? -1 : deltaY
< 0 ? 1 : 0;
407 if (timestamp
- that
.startTime
> 300) {
408 that
.startTime
= timestamp
;
409 that
.startX
= that
.x
;
410 that
.startY
= that
.y
;
413 if (that
.options
.onScrollMove
) that
.options
.onScrollMove
.call(that
, e
);
417 if (hasTouch
&& e
.touches
.length
!= 0) return;
420 point
= hasTouch
? e
.changedTouches
[0] : e
,
422 momentumX
= { dist
:0, time
:0 },
423 momentumY
= { dist
:0, time
:0 },
424 duration
= (e
.timeStamp
|| (new Date()).getTime()) - that
.startTime
,
430 that
._unbind(MOVE_EV
);
431 that
._unbind(END_EV
);
432 that
._unbind(CANCEL_EV
);
434 if (that
.options
.onBeforeScrollEnd
) that
.options
.onBeforeScrollEnd
.call(that
, e
);
437 that
.scale
= that
.scale
* that
.lastScale
;
438 that
.x
= that
.originX
- that
.originX
* that
.lastScale
+ that
.x
;
439 that
.y
= that
.originY
- that
.originY
* that
.lastScale
+ that
.y
;
441 that
.scroller
.style
.webkitTransform
= trnOpen
+ that
.x
+ 'px,' + that
.y
+ 'px' + trnClose
+ ' scale(' + that
.scale
+ ')';
449 if (that
.doubleTapTimer
&& that
.options
.zoom
) {
451 clearTimeout(that
.doubleTapTimer
);
452 that
.doubleTapTimer
= null;
453 that
.zoom(that
.pointX
, that
.pointY
, that
.scale
== 1 ? that
.options
.doubleTapZoom
: 1);
455 that
.doubleTapTimer
= setTimeout(function () {
456 that
.doubleTapTimer
= null;
458 // Find the last touched element
459 target
= point
.target
;
460 while (target
.nodeType
!= 1) target
= target
.parentNode
;
462 if (target
.tagName
!= 'SELECT' && target
.tagName
!= 'INPUT' && target
.tagName
!= 'TEXTAREA') {
463 ev
= document
.createEvent('MouseEvents');
464 ev
.initMouseEvent('click', true, true, e
.view
, 1,
465 point
.screenX
, point
.screenY
, point
.clientX
, point
.clientY
,
466 e
.ctrlKey
, e
.altKey
, e
.shiftKey
, e
.metaKey
,
469 target
.dispatchEvent(ev
);
471 }, that
.options
.zoom
? 250 : 0);
477 if (that
.options
.onTouchEnd
) that
.options
.onTouchEnd
.call(that
, e
);
481 if (duration
< 300 && that
.options
.momentum
) {
482 momentumX
= newPosX
? that
._momentum(newPosX
- that
.startX
, duration
, -that
.x
, that
.scrollerW
- that
.wrapperW
+ that
.x
, that
.options
.bounce
? that
.wrapperW
: 0) : momentumX
;
483 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
;
485 newPosX
= that
.x
+ momentumX
.dist
;
486 newPosY
= that
.y
+ momentumY
.dist
;
488 if ((that
.x
> 0 && newPosX
> 0) || (that
.x
< that
.maxScrollX
&& newPosX
< that
.maxScrollX
)) momentumX
= { dist
:0, time
:0 };
489 if ((that
.y
> 0 && newPosY
> 0) || (that
.y
< that
.maxScrollY
&& newPosY
< that
.maxScrollY
)) momentumY
= { dist
:0, time
:0 };
492 if (momentumX
.dist
|| momentumY
.dist
) {
493 newDuration
= m
.max(m
.max(momentumX
.time
, momentumY
.time
), 10);
495 // Do we need to snap?
496 if (that
.options
.snap
) {
497 distX
= newPosX
- that
.absStartX
;
498 distY
= newPosY
- that
.absStartY
;
499 if (m
.abs(distX
) < that
.options
.snapThreshold
&& m
.abs(distY
) < that
.options
.snapThreshold
) { that
.scrollTo(that
.absStartX
, that
.absStartY
, 200); }
501 snap
= that
._snap(newPosX
, newPosY
);
504 newDuration
= m
.max(snap
.time
, newDuration
);
508 that
.scrollTo(newPosX
, newPosY
, newDuration
);
510 if (that
.options
.onTouchEnd
) that
.options
.onTouchEnd
.call(that
, e
);
514 // Do we need to snap?
515 if (that
.options
.snap
) {
516 distX
= newPosX
- that
.absStartX
;
517 distY
= newPosY
- that
.absStartY
;
518 if (m
.abs(distX
) < that
.options
.snapThreshold
&& m
.abs(distY
) < that
.options
.snapThreshold
) that
.scrollTo(that
.absStartX
, that
.absStartY
, 200);
520 snap
= that
._snap(that
.x
, that
.y
);
521 if (snap
.x
!= that
.x
|| snap
.y
!= that
.y
) that
.scrollTo(snap
.x
, snap
.y
, snap
.time
);
524 if (that
.options
.onTouchEnd
) that
.options
.onTouchEnd
.call(that
, e
);
529 if (that
.options
.onTouchEnd
) that
.options
.onTouchEnd
.call(that
, e
);
532 _resetPos: function (time
) {
534 resetX
= that
.x
>= 0 ? 0 : that
.x
< that
.maxScrollX
? that
.maxScrollX
: that
.x
,
535 resetY
= that
.y
>= 0 || that
.maxScrollY
> 0 ? 0 : that
.y
< that
.maxScrollY
? that
.maxScrollY
: that
.y
;
537 if (resetX
== that
.x
&& resetY
== that
.y
) {
540 if (that
.options
.onScrollEnd
) that
.options
.onScrollEnd
.call(that
); // Execute custom code on scroll end
543 if (that
.hScrollbar
&& that
.options
.hideScrollbar
) {
544 if (vendor
== 'webkit') that
.hScrollbarWrapper
.style
[vendor
+ 'TransitionDelay'] = '300ms';
545 that
.hScrollbarWrapper
.style
.opacity
= '0';
547 if (that
.vScrollbar
&& that
.options
.hideScrollbar
) {
548 if (vendor
== 'webkit') that
.vScrollbarWrapper
.style
[vendor
+ 'TransitionDelay'] = '300ms';
549 that
.vScrollbarWrapper
.style
.opacity
= '0';
555 that
.scrollTo(resetX
, resetY
, time
|| 0);
558 _wheel: function (e
) {
562 if ('wheelDeltaX' in e
) {
563 deltaX
= that
.x
+ e
.wheelDeltaX
/ 12,
564 deltaY
= that
.y
+ e
.wheelDeltaY
/ 12;
565 } else if ('detail' in e
) {
566 deltaX
= that
.x
- e
.detail
* 3,
567 deltaY
= that
.y
- e
.detail
* 3;
569 deltaX
= that
.x
- e
.wheelDelta
,
570 deltaY
= that
.y
- e
.wheelDelta
;
573 if (deltaX
> 0) deltaX
= 0;
574 else if (deltaX
< that
.maxScrollX
) deltaX
= that
.maxScrollX
;
576 if (deltaY
> 0) deltaY
= 0;
577 else if (deltaY
< that
.maxScrollY
) deltaY
= that
.maxScrollY
;
579 that
.scrollTo(deltaX
, deltaY
, 0);
582 _mouseout: function (e
) {
583 var t
= e
.relatedTarget
;
590 while (t
= t
.parentNode
) if (t
== this.wrapper
) return;
595 _transitionEnd: function (e
) {
598 if (e
.target
!= that
.scroller
) return;
600 that
._unbind('webkitTransitionEnd');
611 _startAni: function () {
613 startX
= that
.x
, startY
= that
.y
,
614 startTime
= (new Date
).getTime(),
617 if (that
.animating
) return;
619 if (!that
.steps
.length
) {
624 step
= that
.steps
.shift();
626 if (step
.x
== startX
&& step
.y
== startY
) step
.time
= 0;
628 that
.animating
= true;
631 if (that
.options
.useTransition
) {
632 that
._transitionTime(step
.time
);
633 that
._pos(step
.x
, step
.y
);
634 that
.animating
= false;
635 if (step
.time
) that
._bind('webkitTransitionEnd');
639 (function animate () {
640 var now
= (new Date
).getTime(),
643 if (now
>= startTime
+ step
.time
) {
644 that
._pos(step
.x
, step
.y
);
645 that
.animating
= false;
646 if (that
.options
.onAnimationEnd
) that
.options
.onAnimationEnd
.call(that
); // Execute custom code on animation end
651 now
= (now
- startTime
) / step
.time
- 1;
652 easeOut
= m
.sqrt(1 - now
* now
);
653 newX
= (step
.x
- startX
) * easeOut
+ startX
;
654 newY
= (step
.y
- startY
) * easeOut
+ startY
;
655 that
._pos(newX
, newY
);
656 if (that
.animating
) that
.aniTime
= nextFrame(animate
);
660 _transitionTime: function (time
) {
662 this.scroller
.style
[vendor
+ 'TransitionDuration'] = time
;
663 if (this.hScrollbar
) this.hScrollbarIndicator
.style
[vendor
+ 'TransitionDuration'] = time
;
664 if (this.vScrollbar
) this.vScrollbarIndicator
.style
[vendor
+ 'TransitionDuration'] = time
;
667 _momentum: function (dist
, time
, maxDistUpper
, maxDistLower
, size
) {
668 var deceleration
= 0.0006,
669 speed
= m
.abs(dist
) / time
,
670 newDist
= (speed
* speed
) / (2 * deceleration
),
671 newTime
= 0, outsideDist
= 0;
673 // Proportinally reduce speed if we are outside of the boundaries
674 if (dist
> 0 && newDist
> maxDistUpper
) {
675 outsideDist
= size
/ (6 / (newDist
/ speed
* deceleration
));
676 maxDistUpper
= maxDistUpper
+ outsideDist
;
677 speed
= speed
* maxDistUpper
/ newDist
;
678 newDist
= maxDistUpper
;
679 } else if (dist
< 0 && newDist
> maxDistLower
) {
680 outsideDist
= size
/ (6 / (newDist
/ speed
* deceleration
));
681 maxDistLower
= maxDistLower
+ outsideDist
;
682 speed
= speed
* maxDistLower
/ newDist
;
683 newDist
= maxDistLower
;
686 newDist
= newDist
* (dist
< 0 ? -1 : 1);
687 newTime
= speed
/ deceleration
;
689 return { dist
: newDist
, time
: m
.round(newTime
) };
692 _offset: function (el
) {
693 var left
= -el
.offsetLeft
,
696 while (el
= el
.offsetParent
) {
697 left
-= el
.offsetLeft
;
701 if (el
!= this.wrapper
) {
706 return { left
: left
, top
: top
};
709 _snap: function (x
, y
) {
716 page
= that
.pagesX
.length
- 1;
717 for (i
=0, l
=that
.pagesX
.length
; i
<l
; i
++) {
718 if (x
>= that
.pagesX
[i
]) {
723 if (page
== that
.currPageX
&& page
> 0 && that
.dirX
< 0) page
--;
724 x
= that
.pagesX
[page
];
725 sizeX
= m
.abs(x
- that
.pagesX
[that
.currPageX
]);
726 sizeX
= sizeX
? m
.abs(that
.x
- x
) / sizeX
* 500 : 0;
727 that
.currPageX
= page
;
730 page
= that
.pagesY
.length
-1;
731 for (i
=0; i
<page
; i
++) {
732 if (y
>= that
.pagesY
[i
]) {
737 if (page
== that
.currPageY
&& page
> 0 && that
.dirY
< 0) page
--;
738 y
= that
.pagesY
[page
];
739 sizeY
= m
.abs(y
- that
.pagesY
[that
.currPageY
]);
740 sizeY
= sizeY
? m
.abs(that
.y
- y
) / sizeY
* 500 : 0;
741 that
.currPageY
= page
;
743 // Snap with constant speed (proportional duration)
744 time
= m
.round(m
.max(sizeX
, sizeY
)) || 200;
746 return { x
: x
, y
: y
, time
: time
};
749 _bind: function (type
, el
, bubble
) {
750 (el
|| this.scroller
).addEventListener(type
, this, !!bubble
);
753 _unbind: function (type
, el
, bubble
) {
754 (el
|| this.scroller
).removeEventListener(type
, this, !!bubble
);
763 destroy: function () {
766 that
.scroller
.style
[vendor
+ 'Transform'] = '';
768 // Remove the scrollbars
769 that
.hScrollbar
= false;
770 that
.vScrollbar
= false;
771 that
._scrollbar('h');
772 that
._scrollbar('v');
774 // Remove the event listeners
775 that
._unbind(RESIZE_EV
);
776 that
._unbind(START_EV
);
777 that
._unbind(MOVE_EV
);
778 that
._unbind(END_EV
);
779 that
._unbind(CANCEL_EV
);
781 if (that
.options
.hasTouch
) {
782 that
._unbind('mouseout', that
.wrapper
);
783 that
._unbind(WHEEL_EV
);
786 if (that
.options
.useTransition
) that
._unbind('webkitTransitionEnd');
788 if (that
.options
.onDestroy
) that
.options
.onDestroy
.call(that
);
791 refresh: function () {
797 if (that
.scale
< that
.options
.zoomMin
) that
.scale
= that
.options
.zoomMin
;
798 that
.wrapperW
= that
.wrapper
.clientWidth
|| 1;
799 that
.wrapperH
= that
.wrapper
.clientHeight
|| 1;
801 that
.scrollerW
= m
.round(that
.scroller
.offsetWidth
* that
.scale
);
802 that
.scrollerH
= m
.round(that
.scroller
.offsetHeight
* that
.scale
);
803 that
.maxScrollX
= that
.wrapperW
- that
.scrollerW
;
804 that
.maxScrollY
= that
.wrapperH
- that
.scrollerH
;
808 that
.hScroll
= that
.options
.hScroll
&& that
.maxScrollX
< 0;
809 that
.vScroll
= that
.options
.vScroll
&& (!that
.options
.bounceLock
&& !that
.hScroll
|| that
.scrollerH
> that
.wrapperH
);
811 that
.hScrollbar
= that
.hScroll
&& that
.options
.hScrollbar
;
812 that
.vScrollbar
= that
.vScroll
&& that
.options
.vScrollbar
&& that
.scrollerH
> that
.wrapperH
;
814 offset
= that
._offset(that
.wrapper
);
815 that
.wrapperOffsetLeft
= -offset
.left
;
816 that
.wrapperOffsetTop
= -offset
.top
;
819 if (typeof that
.options
.snap
== 'string') {
822 els
= that
.scroller
.querySelectorAll(that
.options
.snap
);
823 for (i
=0, l
=els
.length
; i
<l
; i
++) {
824 pos
= that
._offset(els
[i
]);
825 pos
.left
+= that
.wrapperOffsetLeft
;
826 pos
.top
+= that
.wrapperOffsetTop
;
827 that
.pagesX
[i
] = pos
.left
< that
.maxScrollX
? that
.maxScrollX
: pos
.left
* that
.scale
;
828 that
.pagesY
[i
] = pos
.top
< that
.maxScrollY
? that
.maxScrollY
: pos
.top
* that
.scale
;
830 } else if (that
.options
.snap
) {
832 while (pos
>= that
.maxScrollX
) {
833 that
.pagesX
[page
] = pos
;
834 pos
= pos
- that
.wrapperW
;
837 if (that
.maxScrollX
%that
.wrapperW
) that
.pagesX
[that
.pagesX
.length
] = that
.maxScrollX
- that
.pagesX
[that
.pagesX
.length
-1] + that
.pagesX
[that
.pagesX
.length
-1];
842 while (pos
>= that
.maxScrollY
) {
843 that
.pagesY
[page
] = pos
;
844 pos
= pos
- that
.wrapperH
;
847 if (that
.maxScrollY
%that
.wrapperH
) that
.pagesY
[that
.pagesY
.length
] = that
.maxScrollY
- that
.pagesY
[that
.pagesY
.length
-1] + that
.pagesY
[that
.pagesY
.length
-1];
850 // Prepare the scrollbars
851 that
._scrollbar('h');
852 that
._scrollbar('v');
854 that
.scroller
.style
[vendor
+ 'TransitionDuration'] = '0';
859 scrollTo: function (x
, y
, time
, relative
) {
866 if (!step
.length
) step
= [{ x
: x
, y
: y
, time
: time
, relative
: relative
}];
868 for (i
=0, l
=step
.length
; i
<l
; i
++) {
869 if (step
[i
].relative
) { step
[i
].x
= that
.x
- step
[i
].x
; step
[i
].y
= that
.y
- step
[i
].y
; }
870 that
.steps
.push({ x
: step
[i
].x
, y
: step
[i
].y
, time
: step
[i
].time
|| 0 });
876 scrollToElement: function (el
, time
) {
877 var that
= this, pos
;
878 el
= el
.nodeType
? el
: that
.scroller
.querySelector(el
);
881 pos
= that
._offset(el
);
882 pos
.left
+= that
.wrapperOffsetLeft
;
883 pos
.top
+= that
.wrapperOffsetTop
;
885 pos
.left
= pos
.left
> 0 ? 0 : pos
.left
< that
.maxScrollX
? that
.maxScrollX
: pos
.left
;
886 pos
.top
= pos
.top
> 0 ? 0 : pos
.top
< that
.maxScrollY
? that
.maxScrollY
: pos
.top
;
887 time
= time
=== undefined ? m
.max(m
.abs(pos
.left
)*2, m
.abs(pos
.top
)*2) : time
;
889 that
.scrollTo(pos
.left
, pos
.top
, time
);
892 scrollToPage: function (pageX
, pageY
, time
) {
893 var that
= this, x
, y
;
895 if (that
.options
.snap
) {
896 pageX
= pageX
== 'next' ? that
.currPageX
+1 : pageX
== 'prev' ? that
.currPageX
-1 : pageX
;
897 pageY
= pageY
== 'next' ? that
.currPageY
+1 : pageY
== 'prev' ? that
.currPageY
-1 : pageY
;
899 pageX
= pageX
< 0 ? 0 : pageX
> that
.pagesX
.length
-1 ? that
.pagesX
.length
-1 : pageX
;
900 pageY
= pageY
< 0 ? 0 : pageY
> that
.pagesY
.length
-1 ? that
.pagesY
.length
-1 : pageY
;
902 that
.currPageX
= pageX
;
903 that
.currPageY
= pageY
;
904 x
= that
.pagesX
[pageX
];
905 y
= that
.pagesY
[pageY
];
907 x
= -that
.wrapperW
* pageX
;
908 y
= -that
.wrapperH
* pageY
;
909 if (x
< that
.maxScrollX
) x
= that
.maxScrollX
;
910 if (y
< that
.maxScrollY
) y
= that
.maxScrollY
;
913 that
.scrollTo(x
, y
, time
|| 400);
916 disable: function () {
919 this.enabled
= false;
921 // If disabled after touchstart we make sure that there are no left over events
922 this._unbind(MOVE_EV
);
923 this._unbind(END_EV
);
924 this._unbind(CANCEL_EV
);
927 enable: function () {
932 if (this.options
.useTransition
) this._unbind('webkitTransitionEnd');
933 else cancelFrame(this.aniTime
);
936 this.animating
= false;
939 zoom: function (x
, y
, scale
, time
) {
941 relScale
= scale
/ that
.scale
;
943 if (!that
.options
.useTransform
) return;
945 time
= (time
|| 200) + 'ms'
946 x
= x
- that
.wrapperOffsetLeft
- that
.x
;
947 y
= y
- that
.wrapperOffsetTop
- that
.y
;
948 that
.x
= x
- x
* relScale
+ that
.x
;
949 that
.y
= y
- y
* relScale
+ that
.y
;
953 that
.scroller
.style
[vendor
+ 'TransitionDuration'] = time
;
954 that
.scroller
.style
[vendor
+ 'Transform'] = trnOpen
+ that
.x
+ 'px,' + that
.y
+ 'px' + trnClose
+ ' scale(' + scale
+ ')';
960 if (typeof exports
!== 'undefined') exports
.iScroll
= iScroll
;
961 else window
.iScroll
= iScroll
;