ux/Iscroll.js
[roojs1] / ux / Iscroll.js
1 /**
2  * 
3  * Find more about the scrolling function at
4  * http://cubiq.org/iscroll
5  *
6  * Copyright (c) 2010 Matteo Spinelli, http://cubiq.org/
7  * Released under MIT license
8  * http://cubiq.org/dropbox/mit-license.txt
9  * 
10  * Version 3.6 - Last updated: 2010.08.24
11  * 
12  */
13
14 Roo.namespace('Roo.ux'); 
15
16 Roo.ux.Iscroll = function(el, options)
17 {
18     var that = this, i;
19     that.element = typeof el == 'object' ? el : document.getElementById(el);
20     that.wrapper = that.element.parentNode;
21
22     that.element.style.webkitTransitionProperty = '-webkit-transform';
23     that.element.style.webkitTransitionTimingFunction = 'cubic-bezier(0,0,0.25,1)';
24     that.element.style.webkitTransitionDuration = '0';
25     that.element.style.webkitTransform = Roo.ux.Iscroll.translateOpen + '0,0' + Roo.ux.Iscroll.translateClose;
26
27     // Default options
28     that.options = {
29         bounce: Roo.ux.Iscroll.has3d,
30         momentum: Roo.ux.Iscroll.has3d,
31         checkDOMChanges: true,
32         topOnDOMChanges: false,
33         hScrollbar: Roo.ux.Iscroll.has3d,
34         vScrollbar: Roo.ux.Iscroll.has3d,
35         fadeScrollbar: Roo.ux.Iscroll.isIphone || Roo.ux.Iscroll.isIpad || !Roo.ux.Iscroll.isTouch,
36         shrinkScrollbar: Roo.ux.Iscroll.isIphone || Roo.ux.Iscroll.isIpad || !Roo.ux.Iscroll.isTouch,
37         desktopCompatibility: false,
38         overflow: 'hidden',
39         snap: false
40     };
41     
42     // User defined options
43     if (typeof options == 'object') {
44         for (i in options) {
45             that.options[i] = options[i];
46         }
47     }
48
49     if (that.options.desktopCompatibility) {
50         that.options.overflow = 'hidden';
51     }
52     
53     that.wrapper.style.overflow = that.options.overflow;
54     
55     that.refresh();
56
57     window.addEventListener('onorientationchange' in window ? 'orientationchange' : 'resize', that, false);
58
59     if (Roo.ux.Iscroll.isTouch || that.options.desktopCompatibility) {
60         that.element.addEventListener(Roo.ux.Iscroll.START_EVENT, that, false);
61         that.element.addEventListener(Roo.ux.Iscroll.MOVE_EVENT, that, false);
62         that.element.addEventListener(Roo.ux.Iscroll.END_EVENT, that, false);
63     }
64     
65     if (that.options.checkDOMChanges) {
66         that.element.addEventListener('DOMSubtreeModified', that, false);
67     }
68 };
69    
70 Roo.apply(Roo.ux.Iscroll, {
71     has3d       : ('WebKitCSSMatrix' in window && 'm11' in new WebKitCSSMatrix()),
72             // Device sniffing
73     isIphone    : (/iphone/gi).test(navigator.appVersion),
74     isIpad      : (/ipad/gi).test(navigator.appVersion),
75     isAndroid   : (/android/gi).test(navigator.appVersion)
76 });
77
78 Roo.apply(Roo.ux.Iscroll, {
79     isTouch : Roo.ux.Iscroll.isIphone || Roo.ux.Iscroll.isIpad || Roo.ux.Iscroll.isAndroid,
80     // Event sniffing
81     START_EVENT : Roo.ux.Iscroll.isTouch ? 'touchstart' : 'mousedown',
82     MOVE_EVENT : Roo.ux.Iscroll.isTouch ? 'touchmove' : 'mousemove',
83     END_EVENT : Roo.ux.Iscroll.isTouch ? 'touchend' : 'mouseup',
84     // Translate3d helper
85     translateOpen : 'translate' + (Roo.ux.Iscroll.has3d ? '3d(' : '('),
86     translateClose : Roo.ux.Iscroll.has3d ? ',0)' : ')',
87     // Unique ID
88     uid : 0
89 });
90
91
92 Roo.ux.Iscroll.prototype = {
93     x: 0,
94     y: 0,
95     enabled: true,
96
97     handleEvent: function (e) {
98         var that = this;
99
100         switch (e.type) {
101             case START_EVENT:
102                 that.touchStart(e);
103                 break;
104             case MOVE_EVENT:
105                 that.touchMove(e);
106                 break;
107             case END_EVENT:
108                 that.touchEnd(e);
109                 break;
110             case 'webkitTransitionEnd':
111                 that.transitionEnd();
112                 break;
113             case 'orientationchange':
114             case 'resize':
115                 that.refresh();
116                 break;
117             case 'DOMSubtreeModified':
118                 that.onDOMModified(e);
119                 break;
120         }
121     },
122     
123     onDOMModified: function (e) {
124         var that = this;
125
126         // (Hopefully) execute onDOMModified only once
127         if (e.target.parentNode != that.element) {
128             return;
129         }
130
131         setTimeout(function () { that.refresh(); }, 0);
132
133         if (that.options.topOnDOMChanges && (that.x!=0 || that.y!=0)) {
134             that.scrollTo(0,0,'0');
135         }
136     },
137
138     refresh: function () {
139         var that = this,
140             resetX = this.x, resetY = this.y,
141             snap;
142         
143         that.scrollWidth = that.wrapper.clientWidth;
144         that.scrollHeight = that.wrapper.clientHeight;
145         that.scrollerWidth = that.element.offsetWidth;
146         that.scrollerHeight = that.element.offsetHeight;
147         that.maxScrollX = that.scrollWidth - that.scrollerWidth;
148         that.maxScrollY = that.scrollHeight - that.scrollerHeight;
149         that.directionX = 0;
150         that.directionY = 0;
151
152         if (that.scrollX) {
153             if (that.maxScrollX >= 0) {
154                 resetX = 0;
155             } else if (that.x < that.maxScrollX) {
156                 resetX = that.maxScrollX;
157             }
158         }
159         if (that.scrollY) {
160             if (that.maxScrollY >= 0) {
161                 resetY = 0;
162             } else if (that.y < that.maxScrollY) {
163                 resetY = that.maxScrollY;
164             }
165         }
166         // Snap
167         if (that.options.snap) {
168             that.maxPageX = -Math.floor(that.maxScrollX/that.scrollWidth);
169             that.maxPageY = -Math.floor(that.maxScrollY/that.scrollHeight);
170
171             snap = that.snap(resetX, resetY);
172             resetX = snap.x;
173             resetY = snap.y;
174         }
175
176         if (resetX!=that.x || resetY!=that.y) {
177             that.setTransitionTime('0');
178             that.setPosition(resetX, resetY, true);
179         }
180         
181         that.scrollX = that.scrollerWidth > that.scrollWidth;
182         that.scrollY = !that.scrollX || that.scrollerHeight > that.scrollHeight;
183
184         // Update horizontal scrollbar
185         if (that.options.hScrollbar && that.scrollX) {
186             that.scrollBarX = that.scrollBarX || new scrollbar('horizontal', that.wrapper, that.options.fadeScrollbar, that.options.shrinkScrollbar);
187             that.scrollBarX.init(that.scrollWidth, that.scrollerWidth);
188         } else if (that.scrollBarX) {
189             that.scrollBarX = that.scrollBarX.remove();
190         }
191
192         // Update vertical scrollbar
193         if (that.options.vScrollbar && that.scrollY && that.scrollerHeight > that.scrollHeight) {
194             that.scrollBarY = that.scrollBarY || new scrollbar('vertical', that.wrapper, that.options.fadeScrollbar, that.options.shrinkScrollbar);
195             that.scrollBarY.init(that.scrollHeight, that.scrollerHeight);
196         } else if (that.scrollBarY) {
197             that.scrollBarY = that.scrollBarY.remove();
198         }
199     },
200
201     setPosition: function (x, y, hideScrollBars) {
202         var that = this;
203         
204         that.x = x;
205         that.y = y;
206
207         that.element.style.webkitTransform = Roo.ux.Iscroll.translateOpen + that.x + 'px,' + that.y + 'px' + Roo.ux.Iscroll.translateClose;
208
209         // Move the scrollbars
210         if (!hideScrollBars) {
211             if (that.scrollBarX) {
212                 that.scrollBarX.setPosition(that.x);
213             }
214             if (that.scrollBarY) {
215                 that.scrollBarY.setPosition(that.y);
216             }
217         }
218     },
219     
220     setTransitionTime: function(time) {
221         var that = this;
222         
223         time = time || '0';
224         that.element.style.webkitTransitionDuration = time;
225         
226         if (that.scrollBarX) {
227             that.scrollBarX.bar.style.webkitTransitionDuration = time;
228             that.scrollBarX.wrapper.style.webkitTransitionDuration = Roo.ux.Iscroll.has3d && that.options.fadeScrollbar ? '300ms' : '0';
229         }
230         if (that.scrollBarY) {
231             that.scrollBarY.bar.style.webkitTransitionDuration = time;
232             that.scrollBarY.wrapper.style.webkitTransitionDuration = Roo.ux.Iscroll.has3d && that.options.fadeScrollbar ? '300ms' : '0';
233         }
234     },
235         
236     touchStart: function(e) {
237         var that = this,
238             matrix;
239
240         e.preventDefault();
241         e.stopPropagation();
242         
243         if (!that.enabled) {
244             return;
245         }
246
247         that.scrolling = true;          // This is probably not needed, but may be useful if iScroll is used in conjuction with other frameworks
248
249         that.moved = false;
250         that.dist = 0;
251
252         that.setTransitionTime('0');
253
254         // Check if the scroller is really where it should be
255         if (that.options.momentum || that.options.snap) {
256             matrix = new WebKitCSSMatrix(window.getComputedStyle(that.element).webkitTransform);
257             if (matrix.e != that.x || matrix.f != that.y) {
258                 document.removeEventListener('webkitTransitionEnd', that, false);
259                 that.setPosition(matrix.e, matrix.f);
260                 that.moved = true;
261             }
262         }
263
264         that.touchStartX = Roo.ux.Iscroll.isTouch ? e.changedTouches[0].pageX : e.pageX;
265         that.scrollStartX = that.x;
266
267         that.touchStartY = Roo.ux.Iscroll.isTouch ? e.changedTouches[0].pageY : e.pageY;
268         that.scrollStartY = that.y;
269
270         that.scrollStartTime = e.timeStamp;
271
272         that.directionX = 0;
273         that.directionY = 0;
274     },
275     
276     touchMove: function(e) {
277         var that = this,
278             pageX = Roo.ux.Iscroll.isTouch ? e.changedTouches[0].pageX : e.pageX,
279             pageY = Roo.ux.Iscroll.isTouch ? e.changedTouches[0].pageY : e.pageY,
280             leftDelta = that.scrollX ? pageX - that.touchStartX : 0,
281             topDelta = that.scrollY ? pageY - that.touchStartY : 0,
282             newX = that.x + leftDelta,
283             newY = that.y + topDelta;
284
285         if (!that.scrolling) {
286             return;
287         }
288
289         //e.preventDefault();
290         e.stopPropagation();    // Stopping propagation just saves some cpu cycles (I presume)
291
292         that.touchStartX = pageX;
293         that.touchStartY = pageY;
294
295         // Slow down if outside of the boundaries
296         if (newX >= 0 || newX < that.maxScrollX) {
297             newX = that.options.bounce ? Math.round(that.x + leftDelta / 3) : (newX >= 0 || that.maxScrollX>=0) ? 0 : that.maxScrollX;
298         }
299         if (newY >= 0 || newY < that.maxScrollY) { 
300             newY = that.options.bounce ? Math.round(that.y + topDelta / 3) : (newY >= 0 || that.maxScrollY>=0) ? 0 : that.maxScrollY;
301         }
302
303         if (that.dist > 5) {                    // 5 pixels threshold is needed on Android, but also on iPhone looks more natural
304             that.setPosition(newX, newY);
305             that.moved = true;
306             that.directionX = leftDelta > 0 ? -1 : 1;
307             that.directionY = topDelta > 0 ? -1 : 1;
308         } else {
309             that.dist+= Math.abs(leftDelta) + Math.abs(topDelta);
310         }
311     },
312     
313     touchEnd: function(e) {
314         var that = this,
315             time = e.timeStamp - that.scrollStartTime,
316             point = Roo.ux.Iscroll.isTouch ? e.changedTouches[0] : e,
317             target, ev,
318             momentumX, momentumY,
319             newDuration = 0,
320             newPositionX = that.x, newPositionY = that.y,
321             snap;
322
323         if (!that.scrolling) {
324             return;
325         }
326         that.scrolling = false;
327
328         if (!that.moved) {
329             that.resetPosition();
330
331             if (Roo.ux.Iscroll.isTouch) {
332                 // Find the last touched element
333                 target = point.target;
334                 while (target.nodeType != 1) {
335                     target = target.parentNode;
336                 }
337
338                 // Create the fake event
339                 target.style.pointerEvents = 'auto';
340                 ev = document.createEvent('MouseEvents');
341                 ev.initMouseEvent('click', true, true, e.view, 1,
342                     point.screenX, point.screenY, point.clientX, point.clientY,
343                     e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
344                     0, null);
345                 ev._fake = true;
346                 target.dispatchEvent(ev);
347             }
348
349             return;
350         }
351
352         if (!that.options.snap && time > 250) {                 // Prevent slingshot effect
353             that.resetPosition();
354             return;
355         }
356
357         if (that.options.momentum) {
358             momentumX = that.scrollX === true
359                 ? that.momentum(that.x - that.scrollStartX,
360                                 time,
361                                 that.options.bounce ? -that.x + that.scrollWidth/5 : -that.x,
362                                 that.options.bounce ? that.x + that.scrollerWidth - that.scrollWidth + that.scrollWidth/5 : that.x + that.scrollerWidth - that.scrollWidth)
363                 : { dist: 0, time: 0 };
364
365             momentumY = that.scrollY === true
366                 ? that.momentum(that.y - that.scrollStartY,
367                                 time,
368                                 that.options.bounce ? -that.y + that.scrollHeight/5 : -that.y,
369                                 that.options.bounce ? (that.maxScrollY < 0 ? that.y + that.scrollerHeight - that.scrollHeight : 0) + that.scrollHeight/5 : that.y + that.scrollerHeight - that.scrollHeight)
370                 : { dist: 0, time: 0 };
371
372             newDuration = Math.max(Math.max(momentumX.time, momentumY.time), 1);                // The minimum animation length must be 1ms
373             newPositionX = that.x + momentumX.dist;
374             newPositionY = that.y + momentumY.dist;
375         }
376
377         if (that.options.snap) {
378             snap = that.snap(newPositionX, newPositionY);
379             newPositionX = snap.x;
380             newPositionY = snap.y;
381             newDuration = Math.max(snap.time, newDuration);
382         }
383
384         that.scrollTo(newPositionX, newPositionY, newDuration + 'ms');
385     },
386
387     transitionEnd: function () {
388         var that = this;
389         document.removeEventListener('webkitTransitionEnd', that, false);
390         that.resetPosition();
391     },
392
393     resetPosition: function () {
394         var that = this,
395             resetX = that.x,
396             resetY = that.y;
397
398         if (that.x >= 0) {
399             resetX = 0;
400         } else if (that.x < that.maxScrollX) {
401             resetX = that.maxScrollX;
402         }
403
404         if (that.y >= 0 || that.maxScrollY > 0) {
405             resetY = 0;
406         } else if (that.y < that.maxScrollY) {
407             resetY = that.maxScrollY;
408         }
409         
410         if (resetX != that.x || resetY != that.y) {
411             that.scrollTo(resetX, resetY);
412         } else {
413             if (that.moved) {
414                 that.onScrollEnd();             // Execute custom code on scroll end
415                 that.moved = false;
416             }
417
418             // Hide the scrollbars
419             if (that.scrollBarX) {
420                 that.scrollBarX.hide();
421             }
422             if (that.scrollBarY) {
423                 that.scrollBarY.hide();
424             }
425         }
426     },
427     
428     snap: function (x, y) {
429         var that = this, time;
430
431         if (that.directionX > 0) {
432             x = Math.floor(x/that.scrollWidth);
433         } else if (that.directionX < 0) {
434             x = Math.ceil(x/that.scrollWidth);
435         } else {
436             x = Math.round(x/that.scrollWidth);
437         }
438         that.pageX = -x;
439         x = x * that.scrollWidth;
440         if (x > 0) {
441             x = that.pageX = 0;
442         } else if (x < that.maxScrollX) {
443             that.pageX = that.maxPageX;
444             x = that.maxScrollX;
445         }
446
447         if (that.directionY > 0) {
448             y = Math.floor(y/that.scrollHeight);
449         } else if (that.directionY < 0) {
450             y = Math.ceil(y/that.scrollHeight);
451         } else {
452             y = Math.round(y/that.scrollHeight);
453         }
454         that.pageY = -y;
455         y = y * that.scrollHeight;
456         if (y > 0) {
457             y = that.pageY = 0;
458         } else if (y < that.maxScrollY) {
459             that.pageY = that.maxPageY;
460             y = that.maxScrollY;
461         }
462
463         // Snap with constant speed (proportional duration)
464         time = Math.round(Math.max(
465                 Math.abs(that.x - x) / that.scrollWidth * 500,
466                 Math.abs(that.y - y) / that.scrollHeight * 500
467             ));
468             
469         return { x: x, y: y, time: time };
470     },
471
472     scrollTo: function (destX, destY, runtime) {
473         var that = this;
474
475         if (that.x == destX && that.y == destY) {
476             that.resetPosition();
477             return;
478         }
479
480         that.moved = true;
481         that.setTransitionTime(runtime || '350ms');
482         that.setPosition(destX, destY);
483
484         if (runtime==='0' || runtime=='0s' || runtime=='0ms') {
485             that.resetPosition();
486         } else {
487             document.addEventListener('webkitTransitionEnd', that, false);      // At the end of the transition check if we are still inside of the boundaries
488         }
489     },
490     
491     scrollToPage: function (pageX, pageY, runtime) {
492         var that = this, snap;
493
494         if (!that.options.snap) {
495             that.pageX = -Math.round(that.x / that.scrollWidth);
496             that.pageY = -Math.round(that.y / that.scrollHeight);
497         }
498
499         if (pageX == 'next') {
500             pageX = ++that.pageX;
501         } else if (pageX == 'prev') {
502             pageX = --that.pageX;
503         }
504
505         if (pageY == 'next') {
506             pageY = ++that.pageY;
507         } else if (pageY == 'prev') {
508             pageY = --that.pageY;
509         }
510
511         pageX = -pageX*that.scrollWidth;
512         pageY = -pageY*that.scrollHeight;
513
514         snap = that.snap(pageX, pageY);
515         pageX = snap.x;
516         pageY = snap.y;
517
518         that.scrollTo(pageX, pageY, runtime || '500ms');
519     },
520
521     scrollToElement: function (el, runtime) {
522         el = typeof el == 'object' ? el : this.element.querySelector(el);
523
524         if (!el) {
525             return;
526         }
527
528         var that = this,
529             x = that.scrollX ? -el.offsetLeft : 0,
530             y = that.scrollY ? -el.offsetTop : 0;
531
532         if (x >= 0) {
533             x = 0;
534         } else if (x < that.maxScrollX) {
535             x = that.maxScrollX;
536         }
537
538         if (y >= 0) {
539             y = 0;
540         } else if (y < that.maxScrollY) {
541             y = that.maxScrollY;
542         }
543
544         that.scrollTo(x, y, runtime);
545     },
546
547     momentum: function (dist, time, maxDistUpper, maxDistLower) {
548         var friction = 2.5,
549             deceleration = 1.2,
550             speed = Math.abs(dist) / time * 1000,
551             newDist = speed * speed / friction / 1000,
552             newTime = 0;
553
554         // Proportinally reduce speed if we are outside of the boundaries 
555         if (dist > 0 && newDist > maxDistUpper) {
556             speed = speed * maxDistUpper / newDist / friction;
557             newDist = maxDistUpper;
558         } else if (dist < 0 && newDist > maxDistLower) {
559             speed = speed * maxDistLower / newDist / friction;
560             newDist = maxDistLower;
561         }
562         
563         newDist = newDist * (dist < 0 ? -1 : 1);
564         newTime = speed / deceleration;
565
566         return { dist: Math.round(newDist), time: Math.round(newTime) };
567     },
568     
569     onScrollEnd: function () {},
570     
571     destroy: function (full) {
572         var that = this;
573
574         window.removeEventListener('onorientationchange' in window ? 'orientationchange' : 'resize', that, false);              
575         that.element.removeEventListener(START_EVENT, that, false);
576         that.element.removeEventListener(MOVE_EVENT, that, false);
577         that.element.removeEventListener(END_EVENT, that, false);
578         document.removeEventListener('webkitTransitionEnd', that, false);
579
580         if (that.options.checkDOMChanges) {
581             that.element.removeEventListener('DOMSubtreeModified', that, false);
582         }
583
584         if (that.scrollBarX) {
585             that.scrollBarX = that.scrollBarX.remove();
586         }
587
588         if (that.scrollBarY) {
589             that.scrollBarY = that.scrollBarY.remove();
590         }
591         
592         if (full) {
593             that.wrapper.parentNode.removeChild(that.wrapper);
594         }
595         
596         return null;
597     }
598 };
599     
600 Roo.ux.Iscroll.scrollbar = function (dir, wrapper, fade, shrink) {
601     var that = this, style;
602     
603     that.dir = dir;
604     that.fade = fade;
605     that.shrink = shrink;
606     that.uid = ++Roo.ux.Iscroll.uid;
607
608     // Create main scrollbar
609     that.bar = document.createElement('div');
610
611     style = 'position:absolute;top:0;left:0;-webkit-transition-timing-function:cubic-bezier(0,0,0.25,1);pointer-events:none;-webkit-transition-duration:0;-webkit-transition-delay:0;-webkit-transition-property:-webkit-transform;z-index:10;background:rgba(0,0,0,0.5);' +
612         '-webkit-transform:' + Roo.ux.Iscroll.translateOpen + '0,0' + Roo.ux.Iscroll.translateClose + ';' +
613         (dir == 'horizontal' ? '-webkit-border-radius:3px 2px;min-width:6px;min-height:5px' : '-webkit-border-radius:2px 3px;min-width:5px;min-height:6px');
614
615     that.bar.setAttribute('style', style);
616
617     // Create scrollbar wrapper
618     that.wrapper = document.createElement('div');
619     style = '-webkit-mask:-webkit-canvas(scrollbar' + that.uid + that.dir + ');position:absolute;z-index:10;pointer-events:none;overflow:hidden;opacity:0;-webkit-transition-duration:' + (fade ? '300ms' : '0') + ';-webkit-transition-delay:0;-webkit-transition-property:opacity;' +
620         (that.dir == 'horizontal' ? 'bottom:2px;left:2px;right:7px;height:5px' : 'top:2px;right:2px;bottom:7px;width:5px;');
621     that.wrapper.setAttribute('style', style);
622
623     // Add scrollbar to the DOM
624     that.wrapper.appendChild(that.bar);
625     wrapper.appendChild(that.wrapper);
626 }
627     
628 Roo.ux.Iscroll.scrollbar.prototype = {
629     init: function (scroll, size) {
630         var that = this,
631             ctx;
632
633         // Create scrollbar mask
634         if (that.dir == 'horizontal') {
635             if (that.maxSize != that.wrapper.offsetWidth) {
636                 that.maxSize = that.wrapper.offsetWidth;
637                 ctx = document.getCSSCanvasContext("2d", "scrollbar" + that.uid + that.dir, that.maxSize, 5);
638                 ctx.fillStyle = "rgb(0,0,0)";
639                 ctx.beginPath();
640                 ctx.arc(2.5, 2.5, 2.5, Math.PI/2, -Math.PI/2, false);
641                 ctx.lineTo(that.maxSize-2.5, 0);
642                 ctx.arc(that.maxSize-2.5, 2.5, 2.5, -Math.PI/2, Math.PI/2, false);
643                 ctx.closePath();
644                 ctx.fill();
645             }
646         } else {
647             if (that.maxSize != that.wrapper.offsetHeight) {
648                 that.maxSize = that.wrapper.offsetHeight;
649                 ctx = document.getCSSCanvasContext("2d", "scrollbar" + that.uid + that.dir, 5, that.maxSize);
650                 ctx.fillStyle = "rgb(0,0,0)";
651                 ctx.beginPath();
652                 ctx.arc(2.5, 2.5, 2.5, Math.PI, 0, false);
653                 ctx.lineTo(5, that.maxSize-2.5);
654                 ctx.arc(2.5, that.maxSize-2.5, 2.5, 0, Math.PI, false);
655                 ctx.closePath();
656                 ctx.fill();
657             }
658         }
659
660         that.size = Math.max(Math.round(that.maxSize * that.maxSize / size), 6);
661         that.maxScroll = that.maxSize - that.size;
662         that.toWrapperProp = that.maxScroll / (scroll - size);
663         that.bar.style[that.dir == 'horizontal' ? 'width' : 'height'] = that.size + 'px';
664     },
665     
666     setPosition: function (pos) {
667         var that = this;
668         
669         if (that.wrapper.style.opacity != '1') {
670             that.show();
671         }
672
673         pos = Math.round(that.toWrapperProp * pos);
674
675         if (pos < 0) {
676             pos = that.shrink ? pos + pos*3 : 0;
677             if (that.size + pos < 7) {
678                 pos = -that.size + 6;
679             }
680         } else if (pos > that.maxScroll) {
681             pos = that.shrink ? pos + (pos-that.maxScroll)*3 : that.maxScroll;
682             if (that.size + that.maxScroll - pos < 7) {
683                 pos = that.size + that.maxScroll - 6;
684             }
685         }
686
687         pos = that.dir == 'horizontal'
688             ? Roo.ux.Iscroll.translateOpen + pos + 'px,0' + Roo.ux.Iscroll.translateClose
689             : Roo.ux.Iscroll.translateOpen + '0,' + pos + 'px' + Roo.ux.Iscroll.translateClose;
690
691         that.bar.style.webkitTransform = pos;
692     },
693
694     show: function () {
695         if (Roo.ux.Iscroll.has3d) {
696             this.wrapper.style.webkitTransitionDelay = '0';
697         }
698         this.wrapper.style.opacity = '1';
699     },
700
701     hide: function () {
702         if (Roo.ux.Iscroll.has3d) {
703             this.wrapper.style.webkitTransitionDelay = '350ms';
704         }
705         this.wrapper.style.opacity = '0';
706     },
707     
708     remove: function () {
709         this.wrapper.parentNode.removeChild(this.wrapper);
710         return null;
711     }
712 };
713
714     // Is translate3d compatible?
715
716