5 * new Roo.ux.TouchScroll( { el : id| dom element | roo element } )
11 Roo.ux.TouchScroll = function (cfg) {
13 this.el = Roo.get(this.el);
17 Roo.apply(Roo.ux.TouchScroll.prototype, {
19 // Define default scroll settings
23 elastic: !navigator.userAgent.match(/android/i),
30 iPadMomentumDamp: 0.95,
31 iPadMomentumTime: 1200,
32 touchTags: ['select', 'input', 'textarea'],
39 // Prevent double-init, just update instead
46 // Define element variables
48 scrollY = -this.el.getY(),
59 isiPad = navigator.platform.match(/ipad/i) ? true : false,
60 hasMatrix = 'WebKitCSSMatrix' in window,
61 has3d = hasMatrix && 'm11' in new WebKitCSSMatrix();
63 // Keep bottom of scroll area at the bottom on resize
64 var update = this.update = function() {
65 height = el.getHeight();
66 if (el.dom.scrollHeight) {
67 scrollHeight = el.dom.scrollHeight;
69 scrollHeight = el.dom.getAttribute('scrollHeight');
71 if (scrollHeight < height) {
72 scrollHeight = height;
74 maxHeight = height - scrollHeight;
75 clearTimeout(timeoutID);
79 // Set up initial variables
82 // Set up transform CSS
84 '-webkit-transition-property': '-webkit-transform',
85 '-webkit-transition-timing-function': 'cubic-bezier(0,0,0.2,1)',
86 '-webkit-transition-duration': '0',
87 '-webkit-transform': cssTranslate(scrollY)
90 // Listen for screen size change event
91 window.addEventListener('onorientationchange' in window ?
92 'orientationchange' : 'resize', update, false);
94 // Listen for touch events
95 this.el.on('touchstart.touchScroll', touchStart);
96 this.el.on('touchmove.touchScroll', touchMove);
97 this.el.on('touchend.touchScroll touchcancel.touchScroll', touchEnd);
98 this.el.on('webkitTransitionEnd.touchScroll', transitionEnd);
100 // Set the position of the scroll area using transform CSS
101 var setPosition = this.setPosition = function(y) {
103 this.el.setStyle('-webkit-transform', cssTranslate(scrollY));
106 // Transform using a 3D translate if available
107 function cssTranslate(y) {
108 return 'translate' + (has3d ? '3d(0,' : '(0,') + y + 'px' + (has3d ? ',0)' : ')');
111 // Set CSS transition time
112 function setTransitionTime(time) {
114 this.el.setStyle('-webkit-transition-duration', time + 'ms');
117 // Get the actual pixel position made by transform CSS
118 function getPosition() {
120 var transform = window.getComputedStyle(this.el.dom).webkitTransform;
121 if (!!transform && transform !== 'none') {
122 var matrix = new WebKitCSSMatrix(transform);
129 // Expose getPosition API
130 this.getPosition = function() {
131 return getPosition();
134 // Bounce back to the bounds after momentum scrolling
135 function reboundScroll() {
137 scrollTo(0, o.reboundTime);
138 } else if (scrollY < maxHeight) {
139 scrollTo(maxHeight, o.reboundTime);
143 // Stop everything once the CSS transition in complete
144 function transitionEnd() {
150 clearTimeout(timeoutID);
153 // Limit the scrolling to within the bounds
154 function clampScroll(poll) {
155 if (!hasMatrix || bouncing) {
160 pollY = getPosition();
164 // Slow down outside top bound
167 momentumScroll(pollY - oldY, o.elasticDamp, 1, height, o.elasticTime);
169 // Stop outside top bound
170 setTransitionTime(0);
173 } else if (pollY < maxHeight) {
175 // Slow down outside bottom bound
178 momentumScroll(pollY - oldY, o.elasticDamp, 1, height, o.elasticTime);
180 // Stop outside bottom bound
181 setTransitionTime(0);
182 setPosition(maxHeight);
185 // Poll the computed position to check if element is out of bounds
186 timeoutID = setTimeout(clampScroll, 20, true);
190 // Animate to a position using CSS
191 function scrollTo(destY, time) {
192 if (destY === scrollY) {
197 setTransitionTime(time);
201 // Perform a momentum-based scroll using CSS
202 function momentumScroll(d, k, minDist, maxDist, t) {
203 var ad = Math.abs(d),
206 // Calculate the total distance
212 // Limit to within min and max distances
223 // If outside the bounds, don't go too far
225 if (dy > height * 2) {
228 } else if (dy < maxHeight - height * 2) {
229 dy = maxHeight - height * 2;
234 scrollTo(Math.round(dy), t);
240 // Get the touch points from this event
241 function getTouches(e) {
242 if (e.originalEvent) {
243 if (e.originalEvent.touches && e.originalEvent.touches.length) {
244 return e.originalEvent.touches;
245 } else if (e.originalEvent.changedTouches && e.originalEvent.changedTouches.length) {
246 return e.originalEvent.changedTouches;
252 // Dispatches a fake mouse event from a touch event
253 function dispatchMouseEvent(name, touch, target) {
254 var e = document.createEvent('MouseEvent');
255 e.initMouseEvent(name, true, true, touch.view, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
256 target.dispatchEvent(e);
259 // Find the root node of this target
260 function getRootNode(target) {
261 while (target.nodeType !== 1) {
262 target = target.parentNode;
267 // Perform a touch start event
268 function touchStart(e) {
269 // Allow certain HTML tags to receive touch events
270 if ($.inArray(e.target.tagName.toLowerCase(), o.touchTags) !== -1) {
274 // Stop the default touches
278 var touch = getTouches(e)[0];
280 // Dispatch a fake mouse down event
281 dispatchMouseEvent('mousedown', touch, getRootNode(touch.target));
287 clearTimeout(timeoutID);
288 setTransitionTime(0);
290 // Check scroll position
292 var y = getPosition();
299 touchY = touch.pageY - scrollY;
302 // Perform a touch move event
303 function touchMove(e) {
308 var dy = getTouches(e)[0].pageY - touchY;
310 // Elastic-drag or stop when moving outside of boundaries
317 } else if (dy < maxHeight) {
319 dy = (dy + maxHeight) / 2;
325 movedY = dy - scrollY;
330 // Perform a touch end event
331 function touchEnd(e) {
339 // Ease back to within boundaries
340 if (scrollY > 0 || scrollY < maxHeight) {
342 } else if (o.momentum) {
343 // Free scroll with momentum
344 momentumScroll(movedY, isiPad ? o.iPadMomentumDamp : o.momentumDamp, 40, 2000, isiPad ? o.iPadMomentumTime : o.momentumTime);
347 var touch = getTouches(e)[0],
348 target = getRootNode(touch.target);
350 // Dispatch fake mouse up and click events if this touch event did not move
351 dispatchMouseEvent('mouseup', touch, target);
352 dispatchMouseEvent('click', touch, target);
360 return this.each(function() {
365 getPosition: function() {
367 this.each(function() {
368 a.push(-this.getPosition());
373 setPosition: function(y) {
374 return this.each(function() {
375 this.setPosition(-y);
381 // Public method for touchScroll
382 $.fn.touchScroll = function(method) {
383 if (methods[method]) {
384 return methods[method].apply(this, Array.prototype.slice.call(arguments, 1));
385 } else if (typeof method === 'object' || !method) {
386 return methods.init.apply(this, arguments);
388 $.error('Method ' + method + ' does not exist on jQuery.touchScroll');