ux/Tscroll.js
authorAlan Knowles <alan@roojs.com>
Fri, 23 Dec 2011 13:16:14 +0000 (21:16 +0800)
committerAlan Knowles <alan@roojs.com>
Fri, 23 Dec 2011 13:16:14 +0000 (21:16 +0800)
ux/Tscroll.js

index e69de29..3e066e3 100644 (file)
@@ -0,0 +1,392 @@
+/**
+ *
+ * usage:
+ *
+ * new Roo.ux.TouchScroll( { el : id| dom element | roo element } )
+ *
+ *
+ */
+
+
+Roo.ux.TouchScroll = function (cfg) {
+    Roo.apply(this,cfg);
+    this.el = Roo.get(this.el);
+    this.init();
+}
+    
+Roo.apply(Roo.ux.TouchScroll.prototype, {
+    
+       // Define default scroll settings
+        
+        y: 0,
+        scrollHeight: 0,
+        elastic: !navigator.userAgent.match(/android/i),
+        momentum: true,
+        elasticDamp: 0.6,
+        elasticTime: 50,
+        reboundTime: 400,
+        momentumDamp: 0.9,
+        momentumTime: 300,
+        iPadMomentumDamp: 0.95,
+        iPadMomentumTime: 1200,
+        touchTags: ['select', 'input', 'textarea'],
+       
+        _init : false,
+        
+        init: function( ) {
+                
+                
+              // Prevent double-init, just update instead
+            if (this._init) {
+                return this.update();
+            }
+            
+            this._init = true;
+                        
+            // Define element variables
+            var 
+                scrollY = -this.el.getY(),
+                touchY = 0,
+                movedY = 0,
+                pollY = 0,
+                height = 0,
+                maxHeight = 0,
+                scrollHeight = 0,
+                scrolling = false,
+                bouncing = false,
+                moved = false,
+                timeoutID,
+                isiPad = navigator.platform.match(/ipad/i) ? true : false,
+                hasMatrix = 'WebKitCSSMatrix' in window,
+                has3d = hasMatrix && 'm11' in new WebKitCSSMatrix();
+                        
+                // Keep bottom of scroll area at the bottom on resize
+                var update = this.update = function() {
+                        height = el.getHeight();
+                        if (el.dom.scrollHeight) {
+                                scrollHeight = el.dom.scrollHeight;
+                        } else {
+                                scrollHeight = el.dom.getAttribute('scrollHeight');
+                        }
+                        if (scrollHeight < height) {
+                                scrollHeight = height;
+                        }
+                        maxHeight = height - scrollHeight;
+                        clearTimeout(timeoutID);
+                        clampScroll(false);
+                };
+                        
+                // Set up initial variables
+                update();
+                
+                        // Set up transform CSS
+                this.el.setStyle({
+                    '-webkit-transition-property': '-webkit-transform',
+                    '-webkit-transition-timing-function': 'cubic-bezier(0,0,0.2,1)',
+                    '-webkit-transition-duration': '0',
+                    '-webkit-transform': cssTranslate(scrollY)
+                });
+                        
+                        // Listen for screen size change event
+                window.addEventListener('onorientationchange' in window ?
+                                'orientationchange' : 'resize', update, false);
+                        
+                // Listen for touch events
+                this.el.on('touchstart.touchScroll', touchStart);
+                this.el.on('touchmove.touchScroll', touchMove);
+                this.el.on('touchend.touchScroll touchcancel.touchScroll', touchEnd);
+                this.el.on('webkitTransitionEnd.touchScroll', transitionEnd);
+                        
+                        // Set the position of the scroll area using transform CSS
+                var setPosition = this.setPosition = function(y) {
+                        scrollY = y;
+                        this.el.setStyle('-webkit-transform', cssTranslate(scrollY));
+                };
+                
+                        // Transform using a 3D translate if available
+                function cssTranslate(y) {
+                        return 'translate' + (has3d ? '3d(0,' : '(0,') + y + 'px' + (has3d ? ',0)' : ')');
+                }
+                        
+                        // Set CSS transition time
+                function setTransitionTime(time) {
+                        time = time || '0';
+                        this.el.setStyle('-webkit-transition-duration', time + 'ms');
+                }
+    
+                            // Get the actual pixel position made by transform CSS
+                function getPosition() {
+                        if (hasMatrix) {
+                                var transform = window.getComputedStyle(this.el.dom).webkitTransform;
+                                if (!!transform && transform !== 'none') {
+                                        var matrix = new WebKitCSSMatrix(transform);
+                                        return matrix.f;
+                                }
+                        }
+                        return scrollY;
+                }
+                            
+                    // Expose getPosition API
+                this.getPosition = function() {
+                        return getPosition();
+                };
+
+                        // Bounce back to the bounds after momentum scrolling
+                        function reboundScroll() {
+                                if (scrollY > 0) {
+                                        scrollTo(0, o.reboundTime);
+                                } else if (scrollY < maxHeight) {
+                                        scrollTo(maxHeight, o.reboundTime);
+                                }
+                        }
+
+                        // Stop everything once the CSS transition in complete
+                        function transitionEnd() {
+                                if (bouncing) {
+                                        bouncing = false;
+                                        reboundScroll();
+                                }
+
+                                clearTimeout(timeoutID);
+                        }
+                        
+                        // Limit the scrolling to within the bounds
+                        function clampScroll(poll) {
+                                if (!hasMatrix || bouncing) {
+                                        return;
+                                }
+
+                                var oldY = pollY;
+                                pollY = getPosition();
+                                
+                                if (pollY > 0) {
+                                        if (o.elastic) {
+                                                // Slow down outside top bound
+                                                bouncing = true;
+                                                scrollY = 0;
+                                                momentumScroll(pollY - oldY, o.elasticDamp, 1, height, o.elasticTime);
+                                        } else {
+                                                // Stop outside top bound
+                                                setTransitionTime(0);
+                                                setPosition(0);
+                                        }
+                                } else if (pollY < maxHeight) {
+                                        if (o.elastic) {
+                                                // Slow down outside bottom bound
+                                                bouncing = true;
+                                                scrollY = maxHeight;
+                                                momentumScroll(pollY - oldY, o.elasticDamp, 1, height, o.elasticTime);
+                                        } else {
+                                                // Stop outside bottom bound
+                                                setTransitionTime(0);
+                                                setPosition(maxHeight);
+                                        }
+                                } else if (poll) {
+                                        // Poll the computed position to check if element is out of bounds
+                                        timeoutID = setTimeout(clampScroll, 20, true);
+                                }
+                        }
+                        
+                        // Animate to a position using CSS
+                        function scrollTo(destY, time) {
+                                if (destY === scrollY) {
+                                        return;
+                                }
+                                
+                                moved = true;
+                                setTransitionTime(time);
+                                setPosition(destY);
+                        }
+                        
+                        // Perform a momentum-based scroll using CSS
+                        function momentumScroll(d, k, minDist, maxDist, t) {
+                                var ad = Math.abs(d),
+                                        dy = 0;
+                                
+                                // Calculate the total distance
+                                while (ad > 0.1) {
+                                        ad *= k;
+                                        dy += ad;
+                                }
+                                
+                                // Limit to within min and max distances
+                                if (dy > maxDist) {
+                                        dy = maxDist;
+                                }
+                                if (dy > minDist) {
+                                        if (d < 0) {
+                                                dy = -dy;
+                                        }
+                                        
+                                        dy += scrollY;
+                                        
+                                        // If outside the bounds, don't go too far
+                                        if (height > 0) {
+                                                if (dy > height * 2) {
+                                                        var ody = dy;
+                                                        dy = height * 2;
+                                                } else if (dy < maxHeight - height * 2) {
+                                                        dy = maxHeight - height * 2;
+                                                }
+                                        }
+                                
+                                        // Perform scroll
+                                        scrollTo(Math.round(dy), t);
+                                }
+                                
+                                clampScroll(true);
+                        }
+                        
+                        // Get the touch points from this event
+                        function getTouches(e) {
+                                if (e.originalEvent) {
+                                        if (e.originalEvent.touches && e.originalEvent.touches.length) {
+                                                return e.originalEvent.touches;
+                                        } else if (e.originalEvent.changedTouches && e.originalEvent.changedTouches.length) {
+                                                return e.originalEvent.changedTouches;
+                                        }
+                                }
+                                return e.touches;
+                        }
+                        
+                        // Dispatches a fake mouse event from a touch event
+                        function dispatchMouseEvent(name, touch, target) {
+                                var e = document.createEvent('MouseEvent');
+                                e.initMouseEvent(name, true, true, touch.view, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
+                                target.dispatchEvent(e);
+                        }
+                        
+                        // Find the root node of this target
+                        function getRootNode(target) {
+                                while (target.nodeType !== 1) {
+                                        target = target.parentNode;
+                                }
+                                return target;
+                        }
+                        
+                        // Perform a touch start event
+                        function touchStart(e) {
+                                // Allow certain HTML tags to receive touch events
+                                if ($.inArray(e.target.tagName.toLowerCase(), o.touchTags) !== -1) {
+                                        return;
+                                }
+                                
+                                // Stop the default touches
+                                e.preventDefault();
+                                e.stopPropagation();
+                                
+                                var touch = getTouches(e)[0];
+                                
+                                // Dispatch a fake mouse down event            
+                                dispatchMouseEvent('mousedown', touch, getRootNode(touch.target));
+                                
+                                scrolling = true;
+                                moved = false;
+                                movedY = 0;
+                                
+                                clearTimeout(timeoutID);
+                                setTransitionTime(0);
+                                
+                                // Check scroll position
+                                if (o.momentum) {
+                                        var y = getPosition();
+                                        if (y !== scrollY) {
+                                                setPosition(y);
+                                                moved = true;
+                                        }
+                                }
+
+                                touchY = touch.pageY - scrollY;
+                        }
+                        
+                        // Perform a touch move event
+                        function touchMove(e) {
+                                if (!scrolling) {
+                                        return;
+                                }
+                                
+                                var dy = getTouches(e)[0].pageY - touchY;
+                                
+                                // Elastic-drag or stop when moving outside of boundaries
+                                if (dy > 0) {
+                                        if (o.elastic) {
+                                                dy /= 2;
+                                        } else {
+                                                dy = 0;
+                                        }
+                                } else if (dy < maxHeight) {
+                                        if (o.elastic) {
+                                                dy = (dy + maxHeight) / 2;
+                                        } else {
+                                                dy = maxHeight;
+                                        }
+                                }
+                                
+                                movedY = dy - scrollY;
+                                moved = true;
+                                setPosition(dy);
+                        }
+                        
+                        // Perform a touch end event
+                        function touchEnd(e) {
+                                if (!scrolling) {
+                                        return;
+                                }
+                                
+                                scrolling = false;
+                                
+                                if (moved) {
+                                        // Ease back to within boundaries
+                                        if (scrollY > 0 || scrollY < maxHeight) {
+                                                reboundScroll();
+                                        } else if (o.momentum) {
+                                                // Free scroll with momentum
+                                                momentumScroll(movedY, isiPad ? o.iPadMomentumDamp : o.momentumDamp, 40, 2000, isiPad ? o.iPadMomentumTime : o.momentumTime);
+                                        }                      
+                                } else {
+                                        var touch = getTouches(e)[0],
+                                                target = getRootNode(touch.target);
+                                        
+                                        // Dispatch fake mouse up and click events if this touch event did not move
+                                        dispatchMouseEvent('mouseup', touch, target);
+                                        dispatchMouseEvent('click', touch, target);
+                                }
+                        }
+                
+                });
+        },
+        
+        update: function() {
+                return this.each(function() {
+                        this.update();
+                });
+        },
+        
+        getPosition: function() {
+                var a = [];
+                this.each(function() {
+                        a.push(-this.getPosition());
+                });
+                return a;
+        },
+        
+        setPosition: function(y) {
+                return this.each(function() {
+                        this.setPosition(-y);
+                });
+        }
+        
+};
+       
+       // Public method for touchScroll
+       $.fn.touchScroll = function(method) {
+           if (methods[method]) {
+                       return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
+               } else if (typeof method === 'object' || !method) {
+                       return methods.init.apply(this, arguments);
+               } else {
+                       $.error('Method ' +  method + ' does not exist on jQuery.touchScroll');
+               }
+       };
+
+})(jQuery);
\ No newline at end of file