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