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