From efc7d83be7caae1d518d418c6cc4743ceac9a8c8 Mon Sep 17 00:00:00 2001 From: Kris Zyp Date: Mon, 9 Apr 2012 15:45:53 -0600 Subject: [PATCH 1/6] Add support for leveraging accelerated transforms on iOS <= 4 Add support for using native touch scrolling on iOS >=5 and Android >=4 (monitoring touch to fire pseudo scroll events for OnDemandList) (Still uses plain scrollLeft/Top on older android, transforms don't seem to help) Fix to use constant friction deceleration Other glide fixes Separate out dojox mobile test page, and miminal mobile test page --- OnDemandList.js | 7 +-- TouchScroll.js | 112 ++++++++++++++++++++++++++---------- css/dgrid.css | 4 +- test/dojox_mobile_grid.html | 69 ++++++++++++++++++++++ test/mobile_grid.html | 80 ++++++++++---------------- 5 files changed, 187 insertions(+), 85 deletions(-) create mode 100644 test/dojox_mobile_grid.html diff --git a/OnDemandList.js b/OnDemandList.js index e12b5090c..49583103a 100644 --- a/OnDemandList.js +++ b/OnDemandList.js @@ -38,8 +38,7 @@ return declare([List, _StoreMixin], { this.inherited(arguments); var self = this; // check visibility on scroll events - listen(this.bodyNode, "scroll", - miscUtil.throttleDelayed(function(event){ self._processScroll(event); }, + listen(this.bodyNode, "scroll,touch-scroll", miscUtil.throttleDelayed(function(event){ self._processScroll(event); }, null, this.pagingDelay)); }, @@ -159,12 +158,12 @@ return declare([List, _StoreMixin], { lastScrollTop: 0, _processScroll: function(){ // summary: - // Checks to make sure that everything in the viewable area has been + // Checks to make sure that everything in te viewable area has been // downloaded, and triggering a request for the necessary data when needed. var grid = this, scrollNode = grid.bodyNode, transform = grid.contentNode.style.webkitTransform, - visibleTop = scrollNode.scrollTop + (transform ? -transform.match(/translate[\w]*\(.*?,(.*?)px/)[1] : 0), + visibleTop = scrollNode.instantScrollTop || scrollNode.scrollTop, visibleBottom = scrollNode.offsetHeight + visibleTop, priorPreload, preloadNode, preload = grid.preload, lastScrollTop = grid.lastScrollTop; diff --git a/TouchScroll.js b/TouchScroll.js index 5d0ce0347..35acca7ca 100644 --- a/TouchScroll.js +++ b/TouchScroll.js @@ -1,12 +1,16 @@ -define(["dojo/_base/declare", "dojo/on"], -function(declare, on){ +define(["dojo/_base/declare", "dojo/on", "dojo/has", "put-selector/put"], +function(declare, on, has, put){ + var userAgent = navigator.userAgent; + // have you some sniffing to guess if it has touch scrolling and accelerated transforms + has.add("touch-scrolling", document.documentElement.style.WebkitOverflowScrolling !== undefined || parseFloat(userAgent.split("Android ")[1]) >= 4); + has.add("accelerated-transform", !!userAgent.match(/like Mac/)); var bodyTouchListener, // stores handle to body touch handler once connected - timerRes = 15, // ms between drag velocity measurements and animation "ticks" + timerRes = 25, // ms between drag velocity measurements and animation "ticks" touches = 0, // records number of touches on document current = {}, // records info for widget currently being scrolled glide = {}, // records info for widgets that are in "gliding" state - glideThreshold = 1; // speed (in px) below which to stop glide + glideThreshold = 0.021; // speed (in px) below which to stop glide function updatetouchcount(evt){ touches = evt.touches.length; @@ -22,7 +26,6 @@ function(declare, on){ clearTimeout(g.timer); delete glide[id]; } - // check "global" touches count (which hasn't counted this touch yet) if(touches > 0){ return; } // ignore multitouch gestures @@ -31,8 +34,7 @@ function(declare, on){ widget: evt.widget, node: this, startX: this.scrollLeft + t.pageX, - startY: this.scrollTop + t.pageY, - timer: setTimeout(calcTick, timerRes) + startY: this.scrollTop + t.pageY }; } function ontouchmove(evt){ @@ -41,38 +43,88 @@ function(declare, on){ t = evt.touches[0]; // snuff event and scroll the area - evt.preventDefault(); - evt.stopPropagation(); - this.scrollLeft = current.startX - t.pageX; - this.scrollTop = current.startY - t.pageY; + if(!has("touch-scrolling")){ + evt.preventDefault(); + evt.stopPropagation(); + } + scroll(this, current.startX - t.pageX, current.startY - t.pageY); + calcVelocity(); } function ontouchend(evt){ if(touches != 1 || !current){ return; } - current.timer && clearTimeout(current.timer); startGlide(current); current = null; } - + + function scroll(node, x, y){ + // do the actual scrolling + var hasTouchScrolling = has("touch-scrolling"); + if(!hasTouchScrolling && has("accelerated-transform")){ + // we have hardward acceleration of transforms, so we will do the fast scrolling + // by setting the transform style with a translate3d + var transformNode = node.firstChild; + var lastScrollLeft = node.scrollLeft; + var lastScrollTop = node.scrollTop; + // store the current scroll position + node.instantScrollLeft = x; + node.instantScrollTop = y; + // set the style transform + transformNode.style.WebkitTransform = "translate3d(" + (Math.max(Math.min(0, -x), node.offsetWidth - node.scrollWidth) + node.scrollLeft) + "px," + + (Math.max(Math.min(0, -y), node.offsetHeight - node.scrollHeight) + node.scrollTop) + "px,0)"; + // now every half a second actually update the scroll position so that the scroll + // monitors (like OnDemandList) receive events and scroll positions to work with + if(!node._scrollWaiting){ + node._scrollWaiting = true; + setTimeout(function(){ + node._scrollWaiting = false; + // reset the transform since we are updating the actual scroll position + transformNode.style.WebkitTransform = "translate3d(0,0,0)"; + // get the latest effective scroll position + node.scrollLeft = node.instantScrollLeft; + node.scrollTop = node.instantScrollTop; + // reset these so they aren't used anymore + node.instantScrollLeft = 0; + node.instantScrollTop = 0; + }, 500); + } + }else{ + // update scroll position immediately (note we may be using browser's touch scroll + var scrollPrefix = hasTouchScrolling ? "instantScroll" : "scroll"; + node[scrollPrefix + "Left"] = x; + node[scrollPrefix + "Top"] = y; + if(hasTouchScrolling){ + // if we are using browser's touch scroll, we fire our own scroll events + on.emit(node, "touch-scroll", {}); + } + } + + + } // glide-related functions - function calcTick(){ + function calcVelocity(){ // Calculates current speed of touch drag - var node, x, y; + var node, x, y, now; if(!current){ return; } // no currently-scrolling widget; abort node = current.node; - x = node.scrollLeft; - y = node.scrollTop; + x = node.instantScrollLeft || node.scrollLeft; + y = node.instantScrollTop || node.scrollTop; + now = new Date().getTime(); if("prevX" in current){ // calculate velocity using previous reference point - current.velX = x - current.prevX; - current.velY = y - current.prevY; + var duration = now - current.prevTime; + current.velX = (x - current.prevX) / duration; + current.velY = (y - current.prevY) / duration; + + } + if(!(current.prevTime - now > -150)){ // make sure it is far enough back that we can get a good estimate + // set previous reference point for next iteration + current.prevX = x; + current.prevY = y; + current.prevTime = now; } - // set previous reference point for next iteration - current.prevX = x; - current.prevY = y; - current.timer = setTimeout(calcTick, timerRes); } function startGlide(info){ @@ -88,13 +140,13 @@ function(declare, on){ // performs glide and decelerates according to widget's glideDecel method var g = glide[id], x, y, node, widget, vx, vy, nvx, nvy; // old and new velocities - if(!g){ return; } node = g.node; widget = g.widget; - x = node.scrollLeft; - y = node.scrollTop; + // we use instantScroll... so that OnDemandList has something to pull from to get the current value (needed for ios5 with touch scrolling) + x = node.instantScrollLeft || node.scrollLeft; + y = node.instantScrollTop || node.scrollTop; vx = g.velX; vy = g.velY; nvx = widget.glideDecel(vx); @@ -102,9 +154,8 @@ function(declare, on){ if(Math.abs(nvx) >= glideThreshold || Math.abs(nvy) >= glideThreshold){ // still above stop threshold; update scroll positions - node.scrollLeft += nvx; - node.scrollTop += nvy; - if(node.scrollLeft != x || node.scrollTop != y){ + scroll(node, x + nvx / timerRes * 1000, y + nvy / timerRes * 1000); + if((node.instantScrollLeft || node.scrollLeft) != x || (node.instantScrollTop || node.scrollTop) != y){ // still scrollable; update velocities and schedule next tick g.velX = nvx; g.velY = nvy; @@ -114,6 +165,7 @@ function(declare, on){ } return declare([], { + pagingDelay: 500, startup: function(){ var node = this.touchNode || this.containerNode || this.domNode, widget = this; @@ -136,7 +188,7 @@ function(declare, on){ // Deceleration algorithm. Given a number representing velocity, // returns a new velocity to impose for the next "tick". // (Don't forget that velocity can be positive or negative!) - return n * 0.9; // Number + return n + (n > 0 ? -0.02 : 0.02); // Number } }); }); \ No newline at end of file diff --git a/css/dgrid.css b/css/dgrid.css index 49b534eb8..58ea8a0c8 100644 --- a/css/dgrid.css +++ b/css/dgrid.css @@ -79,20 +79,20 @@ html.has-quirks .dgrid-header-hidden .dgrid-cell { } .dgrid-content { - position: relative; + /*position: relative;*/ height: 99%; } .dgrid-scroller { overflow-x: auto; overflow-y: scroll; + -webkit-overflow-scrolling: touch; position: absolute; top: 0px; margin-top: 25px; /* this will be adjusted programmatically to fit below the header*/ bottom: 0px; width: 100%; } - .dgrid-loading { position: relative; height: 100%; diff --git a/test/dojox_mobile_grid.html b/test/dojox_mobile_grid.html new file mode 100644 index 000000000..5680828c0 --- /dev/null +++ b/test/dojox_mobile_grid.html @@ -0,0 +1,69 @@ + + + + + + + Mobile dgrid test + + + + + + + + + +
+
+

View dgrid

+

dgrid Mobile Test

+ +
    +
  • + View dgrid +
  • +
  • + View 2 +
  • +
+
+ +
+

View 1-2

+
    +
  • + Home +
  • +
  • + View 2 +
  • +
+
+
+
+ +
+

View 2

+ +
+ + diff --git a/test/mobile_grid.html b/test/mobile_grid.html index 77f7a8c13..8cf68652b 100644 --- a/test/mobile_grid.html +++ b/test/mobile_grid.html @@ -6,20 +6,36 @@ Mobile dgrid test - - - - - + + + - -
-
-

View dgrid

-

dgrid Mobile Test

- -
    -
  • - View dgrid -
  • -
  • - View 2 -
  • -
-
- -
-

View 1-2

-
    -
  • - Home -
  • -
  • - View 2 -
  • -
-
-
-
- -
-

View 2

- -
+ +

Header (this is a minimal mobile dgrid with a header and footer)

+
+ From c67a886b77879f0033ccd12bc3ec7b09543dcd19 Mon Sep 17 00:00:00 2001 From: Kris Zyp Date: Tue, 10 Apr 2012 09:53:03 -0600 Subject: [PATCH 2/6] Start on scrollbar --- TouchScroll.js | 13 +++++++++---- css/dgrid.css | 10 ++++++++++ 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/TouchScroll.js b/TouchScroll.js index 35acca7ca..389dd9185 100644 --- a/TouchScroll.js +++ b/TouchScroll.js @@ -29,6 +29,10 @@ function(declare, on, has, put){ // check "global" touches count (which hasn't counted this touch yet) if(touches > 0){ return; } // ignore multitouch gestures + if(!this.scrollbarYNode && !has("touch-scrolling")){ + var scrollbarYNode = this.scrollbarYNode = put(this.parentNode, "div.dgrid-touch-scrollbar-y"); + scrollbarYNode.style.height = this.offsetHeight * this.offsetHeight / this.scrollHeight + "px"; + } t = evt.touches[0]; current = { widget: evt.widget, @@ -59,6 +63,8 @@ function(declare, on, has, put){ function scroll(node, x, y){ // do the actual scrolling var hasTouchScrolling = has("touch-scrolling"); + x = Math.min(Math.max(0, x), node.scrollWidth - node.offsetWidth); + y = Math.min(Math.max(0, y), node.scrollHeight - node.offsetHeight); if(!hasTouchScrolling && has("accelerated-transform")){ // we have hardward acceleration of transforms, so we will do the fast scrolling // by setting the transform style with a translate3d @@ -69,8 +75,8 @@ function(declare, on, has, put){ node.instantScrollLeft = x; node.instantScrollTop = y; // set the style transform - transformNode.style.WebkitTransform = "translate3d(" + (Math.max(Math.min(0, -x), node.offsetWidth - node.scrollWidth) + node.scrollLeft) + "px," - + (Math.max(Math.min(0, -y), node.offsetHeight - node.scrollHeight) + node.scrollTop) + "px,0)"; + transformNode.style.WebkitTransform = "translate3d(" + (node.scrollLeft - x) + "px," + + (node.scrollTop - y) + "px,0)"; // now every half a second actually update the scroll position so that the scroll // monitors (like OnDemandList) receive events and scroll positions to work with if(!node._scrollWaiting){ @@ -97,8 +103,7 @@ function(declare, on, has, put){ on.emit(node, "touch-scroll", {}); } } - - + node.scrollbarYNode.style.top = (y * node.offsetHeight / node.scrollHeight + node.offsetTop) + "px"; } // glide-related functions diff --git a/css/dgrid.css b/css/dgrid.css index 58ea8a0c8..869b86f0a 100644 --- a/css/dgrid.css +++ b/css/dgrid.css @@ -93,6 +93,16 @@ html.has-quirks .dgrid-header-hidden .dgrid-cell { bottom: 0px; width: 100%; } +.dgrid-touch-scrollbar-y { + position: absolute; + right: 0px; + width: 4px; + background-color: #888; + border: 1px solid #333; + border-radius: 3px; + -webkit-box-shadow: 0 0 1px #888; + -webkit-transition: opacity 1s ease-out; +} .dgrid-loading { position: relative; height: 100%; From e71f516dea08fd6b3c73278841649b2dd99f49ea Mon Sep 17 00:00:00 2001 From: Kris Zyp Date: Wed, 11 Apr 2012 13:06:47 -0600 Subject: [PATCH 3/6] Added touch-style scrollbars, improved accuracy of gliding --- OnDemandList.js | 3 +- TouchScroll.js | 75 +++++++++++++++++++++++++++++++------------ css/dgrid.css | 25 +++++++++++---- test/mobile_grid.html | 3 ++ 4 files changed, 77 insertions(+), 29 deletions(-) diff --git a/OnDemandList.js b/OnDemandList.js index 49583103a..7644bb9d3 100644 --- a/OnDemandList.js +++ b/OnDemandList.js @@ -38,7 +38,7 @@ return declare([List, _StoreMixin], { this.inherited(arguments); var self = this; // check visibility on scroll events - listen(this.bodyNode, "scroll,touch-scroll", miscUtil.throttleDelayed(function(event){ self._processScroll(event); }, + listen(this.bodyNode, "scroll", miscUtil.throttleDelayed(function(event){ self._processScroll(event); }, null, this.pagingDelay)); }, @@ -167,7 +167,6 @@ return declare([List, _StoreMixin], { visibleBottom = scrollNode.offsetHeight + visibleTop, priorPreload, preloadNode, preload = grid.preload, lastScrollTop = grid.lastScrollTop; - // XXX: I do not know why this happens. // munging the actual location of the viewport relative to the preload node by a few pixels in either // direction is necessary because at least WebKit on Windows seems to have an error that causes it to diff --git a/TouchScroll.js b/TouchScroll.js index 389dd9185..48ffc151a 100644 --- a/TouchScroll.js +++ b/TouchScroll.js @@ -28,17 +28,31 @@ function(declare, on, has, put){ } // check "global" touches count (which hasn't counted this touch yet) if(touches > 0){ return; } // ignore multitouch gestures - - if(!this.scrollbarYNode && !has("touch-scrolling")){ - var scrollbarYNode = this.scrollbarYNode = put(this.parentNode, "div.dgrid-touch-scrollbar-y"); - scrollbarYNode.style.height = this.offsetHeight * this.offsetHeight / this.scrollHeight + "px"; + if(!has("touch-scrolling")){ + if(this.scrollHeight > this.offsetHeight){ + var scrollbarYNode = this.scrollbarYNode; + if(!scrollbarYNode){ + scrollbarYNode = this.scrollbarYNode = put(this.parentNode, "div.dgrid-touch-scrollbar-y"); + scrollbarYNode.style.height = this.offsetHeight * this.offsetHeight / this.scrollHeight + "px"; + scrollbarYNode.style.top = this.offsetTop + "px"; + } + } + if(this.scrollWidth > this.offsetWidth){ + var scrollbarXNode = this.scrollbarXNode; + if(!scrollbarXNode){ + scrollbarXNode = this.scrollbarXNode = put(this.parentNode, "div.dgrid-touch-scrollbar-x"); + scrollbarXNode.style.width = this.offsetWidth * this.offsetWidth / this.scrollWidth + "px"; + scrollbarXNode.style.left = this.offsetLeft + "px"; + } + } + put(this.parentNode, '!dgrid-touch-scrollbar-fade'); } t = evt.touches[0]; current = { widget: evt.widget, node: this, - startX: this.scrollLeft + t.pageX, - startY: this.scrollTop + t.pageY + startX: (this.instantScrollLeft || this.scrollLeft) + t.pageX, + startY: (this.instantScrollTop || this.scrollTop) + t.pageY }; } function ontouchmove(evt){ @@ -59,12 +73,11 @@ function(declare, on, has, put){ startGlide(current); current = null; } - function scroll(node, x, y){ // do the actual scrolling var hasTouchScrolling = has("touch-scrolling"); - x = Math.min(Math.max(0, x), node.scrollWidth - node.offsetWidth); - y = Math.min(Math.max(0, y), node.scrollHeight - node.offsetHeight); + x = Math.min(Math.max(0.01, x), node.scrollWidth - node.offsetWidth); + y = Math.min(Math.max(0.01, y), node.scrollHeight - node.offsetHeight); if(!hasTouchScrolling && has("accelerated-transform")){ // we have hardward acceleration of transforms, so we will do the fast scrolling // by setting the transform style with a translate3d @@ -98,12 +111,19 @@ function(declare, on, has, put){ var scrollPrefix = hasTouchScrolling ? "instantScroll" : "scroll"; node[scrollPrefix + "Left"] = x; node[scrollPrefix + "Top"] = y; + if(hasTouchScrolling){ // if we are using browser's touch scroll, we fire our own scroll events - on.emit(node, "touch-scroll", {}); + on.emit(node, "scroll", {}); } } - node.scrollbarYNode.style.top = (y * node.offsetHeight / node.scrollHeight + node.offsetTop) + "px"; + if(!hasTouchScrolling){ + // move the scrollbar + var scrollbarXNode = node.scrollbarXNode; + var scrollbarYNode = node.scrollbarYNode; + scrollbarXNode && (scrollbarXNode.style.WebkitTransform = "translate3d(" + (x * node.offsetWidth / node.scrollWidth) + "px,0,0)"); + scrollbarYNode && (scrollbarYNode.style.WebkitTransform = "translate3d(0," + (y * node.offsetHeight / node.scrollHeight) + "px,0)"); + } } // glide-related functions @@ -131,22 +151,27 @@ function(declare, on, has, put){ current.prevTime = now; } } - + var lastGlideTime; function startGlide(info){ // starts glide operation when drag ends var id = info.widget.id, g; - if(!info.velX && !info.velY){ return; } // no glide to perform + if(!info.velX && !info.velY){ + fadeScrollBars(info.node); + return; + } // no glide to perform g = glide[id] = info; // reuse object for widget/node/vel properties g.calcFunc = function(){ calcGlide(id); } + lastGlideTime = new Date().getTime(); g.timer = setTimeout(g.calcFunc, timerRes); } function calcGlide(id){ // performs glide and decelerates according to widget's glideDecel method var g = glide[id], x, y, node, widget, - vx, vy, nvx, nvy; // old and new velocities + vx, vy, nvx, nvy, // old and new velocities + now = new Date().getTime(), + sinceLastGlide = now - lastGlideTime; if(!g){ return; } - node = g.node; widget = g.widget; // we use instantScroll... so that OnDemandList has something to pull from to get the current value (needed for ios5 with touch scrolling) @@ -154,19 +179,28 @@ function(declare, on, has, put){ y = node.instantScrollTop || node.scrollTop; vx = g.velX; vy = g.velY; - nvx = widget.glideDecel(vx); - nvy = widget.glideDecel(vy); + nvx = widget.glideDecel(vx, sinceLastGlide); + nvy = widget.glideDecel(vy, sinceLastGlide); + var continueGlide; if(Math.abs(nvx) >= glideThreshold || Math.abs(nvy) >= glideThreshold){ // still above stop threshold; update scroll positions - scroll(node, x + nvx / timerRes * 1000, y + nvy / timerRes * 1000); + scroll(node, x + nvx * sinceLastGlide, y + nvy * sinceLastGlide); if((node.instantScrollLeft || node.scrollLeft) != x || (node.instantScrollTop || node.scrollTop) != y){ // still scrollable; update velocities and schedule next tick + continueGlide = true; g.velX = nvx; g.velY = nvy; g.timer = setTimeout(g.calcFunc, timerRes); } } + if(!continueGlide){ + fadeScrollBars(node); + } + lastGlideTime = now; + } + function fadeScrollBars(node){ + put(node.parentNode, '.dgrid-touch-scrollbar-fade'); } return declare([], { @@ -188,12 +222,13 @@ function(declare, on, has, put){ "touchstart,touchend,touchcancel", updatetouchcount); } }, - glideDecel: function(n){ + friction: 0.0006, + glideDecel: function(n, sinceLastGlide){ // summary: // Deceleration algorithm. Given a number representing velocity, // returns a new velocity to impose for the next "tick". // (Don't forget that velocity can be positive or negative!) - return n + (n > 0 ? -0.02 : 0.02); // Number + return n + (n > 0 ? -sinceLastGlide : sinceLastGlide) * this.friction; // Number } }); }); \ No newline at end of file diff --git a/css/dgrid.css b/css/dgrid.css index 869b86f0a..cc478809e 100644 --- a/css/dgrid.css +++ b/css/dgrid.css @@ -93,16 +93,27 @@ html.has-quirks .dgrid-header-hidden .dgrid-cell { bottom: 0px; width: 100%; } -.dgrid-touch-scrollbar-y { +.dgrid-touch-scrollbar-y, .dgrid-touch-scrollbar-x { position: absolute; - right: 0px; - width: 4px; - background-color: #888; - border: 1px solid #333; + background-color: rgba(88,88,88,0.97); + opacity: 0.7; + border: 1px solid rgba(88,88,88,1); border-radius: 3px; - -webkit-box-shadow: 0 0 1px #888; - -webkit-transition: opacity 1s ease-out; + -webkit-box-shadow: 0 0 1px rgba(88,88,88,0.4); /* the border's aren't anti-aliased on android, so this smooths it out a bit*/ +} +.dgrid-touch-scrollbar-y { + right: 1px; + width: 3px; } +.dgrid-touch-scrollbar-x { + bottom: 1px; + height: 3px; +} +.dgrid-touch-scrollbar-fade .dgrid-touch-scrollbar-y, .dgrid-touch-scrollbar-fade .dgrid-touch-scrollbar-x { + -webkit-transition: opacity 0.3s ease-out 0.3s; + opacity: 0; +} + .dgrid-loading { position: relative; height: 100%; diff --git a/test/mobile_grid.html b/test/mobile_grid.html index 8cf68652b..8af1b1770 100644 --- a/test/mobile_grid.html +++ b/test/mobile_grid.html @@ -22,6 +22,9 @@ bottom: 0px; height: 30px; } + .field-col1, .field-col4, .field-col5 { +/* width: 400px;*/ + } #grid { height: auto; bottom: 30px; From e711e3c50ace4c012bfd9b78e632ba37c4268046 Mon Sep 17 00:00:00 2001 From: Kris Zyp Date: Wed, 11 Apr 2012 13:43:57 -0600 Subject: [PATCH 4/6] Comment the code more, and lower the timer resolution now that we properly compensate for delayed timer turns --- TouchScroll.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/TouchScroll.js b/TouchScroll.js index 48ffc151a..36dd302ca 100644 --- a/TouchScroll.js +++ b/TouchScroll.js @@ -1,16 +1,16 @@ define(["dojo/_base/declare", "dojo/on", "dojo/has", "put-selector/put"], function(declare, on, has, put){ var userAgent = navigator.userAgent; - // have you some sniffing to guess if it has touch scrolling and accelerated transforms + // have to do some sniffing to guess if it has native overflow touch scrolling and accelerated transforms has.add("touch-scrolling", document.documentElement.style.WebkitOverflowScrolling !== undefined || parseFloat(userAgent.split("Android ")[1]) >= 4); has.add("accelerated-transform", !!userAgent.match(/like Mac/)); var bodyTouchListener, // stores handle to body touch handler once connected - timerRes = 25, // ms between drag velocity measurements and animation "ticks" + timerRes = 10, // ms between drag velocity measurements and animation "ticks" touches = 0, // records number of touches on document current = {}, // records info for widget currently being scrolled glide = {}, // records info for widgets that are in "gliding" state - glideThreshold = 0.021; // speed (in px) below which to stop glide + glideThreshold = 0.021; // speed (in px/ms) below which to stop glide function updatetouchcount(evt){ touches = evt.touches.length; @@ -29,6 +29,7 @@ function(declare, on, has, put){ // check "global" touches count (which hasn't counted this touch yet) if(touches > 0){ return; } // ignore multitouch gestures if(!has("touch-scrolling")){ + // if the scrolling height and width is bigger than the area, than we add scrollbars in each direction if(this.scrollHeight > this.offsetHeight){ var scrollbarYNode = this.scrollbarYNode; if(!scrollbarYNode){ @@ -45,6 +46,7 @@ function(declare, on, has, put){ scrollbarXNode.style.left = this.offsetLeft + "px"; } } + // remove the fade class if we are reusing the scrollbar put(this.parentNode, '!dgrid-touch-scrollbar-fade'); } t = evt.touches[0]; @@ -177,6 +179,7 @@ function(declare, on, has, put){ // we use instantScroll... so that OnDemandList has something to pull from to get the current value (needed for ios5 with touch scrolling) x = node.instantScrollLeft || node.scrollLeft; y = node.instantScrollTop || node.scrollTop; + // note that velocity is measured in pixels per millisecond vx = g.velX; vy = g.velY; nvx = widget.glideDecel(vx, sinceLastGlide); @@ -185,7 +188,7 @@ function(declare, on, has, put){ var continueGlide; if(Math.abs(nvx) >= glideThreshold || Math.abs(nvy) >= glideThreshold){ // still above stop threshold; update scroll positions - scroll(node, x + nvx * sinceLastGlide, y + nvy * sinceLastGlide); + scroll(node, x + nvx * sinceLastGlide, y + nvy * sinceLastGlide); // for each dimension multiply the velocity (px/ms) by the ms elapsed if((node.instantScrollLeft || node.scrollLeft) != x || (node.instantScrollTop || node.scrollTop) != y){ // still scrollable; update velocities and schedule next tick continueGlide = true; @@ -200,6 +203,7 @@ function(declare, on, has, put){ lastGlideTime = now; } function fadeScrollBars(node){ + // add the fade class so that scrollbar fades to transparent put(node.parentNode, '.dgrid-touch-scrollbar-fade'); } @@ -222,6 +226,8 @@ function(declare, on, has, put){ "touchstart,touchend,touchcancel", updatetouchcount); } }, + // friction: Float + // This is the friction deceleration measured in pixels/milliseconds^2 friction: 0.0006, glideDecel: function(n, sinceLastGlide){ // summary: From e0a18765f098a6b366f2779aaee492d05c969b9e Mon Sep 17 00:00:00 2001 From: Kris Zyp Date: Tue, 17 Apr 2012 15:04:16 -0600 Subject: [PATCH 5/6] Properly handle intermediate scroll events on ios 5 and properly skip nodes --- OnDemandList.js | 24 ++++++++++++++---------- TouchScroll.js | 14 +++++++++++--- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/OnDemandList.js b/OnDemandList.js index 7644bb9d3..541990010 100644 --- a/OnDemandList.js +++ b/OnDemandList.js @@ -125,7 +125,7 @@ return declare([List, _StoreMixin], { // if total is 0, IE quirks mode can't handle 0px height for some reason, I don't know why, but we are setting display: none for now preloadNode.style.display = "none"; } - self._processScroll(); // recheck the scroll position in case the query didn't fill the screen + self._processScroll({}); // recheck the scroll position in case the query didn't fill the screen // can remove the loading node now return trs; }); @@ -156,14 +156,14 @@ return declare([List, _StoreMixin], { }, lastScrollTop: 0, - _processScroll: function(){ + _processScroll: function(event){ // summary: // Checks to make sure that everything in te viewable area has been // downloaded, and triggering a request for the necessary data when needed. var grid = this, scrollNode = grid.bodyNode, transform = grid.contentNode.style.webkitTransform, - visibleTop = scrollNode.instantScrollTop || scrollNode.scrollTop, + visibleTop = event.pseudoTouch ? scrollNode.instantScrollTop : scrollNode.scrollTop, visibleBottom = scrollNode.offsetHeight + visibleTop, priorPreload, preloadNode, preload = grid.preload, lastScrollTop = grid.lastScrollTop; @@ -294,14 +294,18 @@ return declare([List, _StoreMixin], { var previous = preload.previous; if(previous){ removeDistantNodes(previous, visibleTop - (previous.node.offsetTop + previous.node.offsetHeight), 'nextSibling'); - if(offset > 0 && previous.node == preloadNode.previousSibling){ - // all of the nodes above were removed - offset = Math.min(preload.count, offset); - preload.previous.count += offset; - adjustHeight(preload.previous); + if(offset > 0){ + if(previous.node == preloadNode.previousSibling){ + // all of the nodes above were removed + offset = Math.min(preload.count, offset); + preload.previous.count += offset; + adjustHeight(preload.previous); + preloadNode.rowIndex += offset; + queryRowsOverlap = 0; + }else{ + count += offset; + } preload.count -= offset; - preloadNode.rowIndex += offset; - queryRowsOverlap = 0; } } options.start = preloadNode.rowIndex - queryRowsOverlap; diff --git a/TouchScroll.js b/TouchScroll.js index 36dd302ca..c3c280819 100644 --- a/TouchScroll.js +++ b/TouchScroll.js @@ -28,7 +28,11 @@ function(declare, on, has, put){ } // check "global" touches count (which hasn't counted this touch yet) if(touches > 0){ return; } // ignore multitouch gestures - if(!has("touch-scrolling")){ + if(has("touch-scrolling")){ + // reset these prior to measurements + this.instantScrollLeft = 0; + this.instantScrollTop = 0; + }else{ // if the scrolling height and width is bigger than the area, than we add scrollbars in each direction if(this.scrollHeight > this.offsetHeight){ var scrollbarYNode = this.scrollbarYNode; @@ -66,7 +70,8 @@ function(declare, on, has, put){ if(!has("touch-scrolling")){ evt.preventDefault(); evt.stopPropagation(); - } + } + scroll(this, current.startX - t.pageX, current.startY - t.pageY); calcVelocity(); } @@ -116,7 +121,9 @@ function(declare, on, has, put){ if(hasTouchScrolling){ // if we are using browser's touch scroll, we fire our own scroll events - on.emit(node, "scroll", {}); + on.emit(node, "scroll", { + pseudoTouch: true + }); } } if(!hasTouchScrolling){ @@ -199,6 +206,7 @@ function(declare, on, has, put){ } if(!continueGlide){ fadeScrollBars(node); + } lastGlideTime = now; } From 13c4df8a5a28bdfb801e4da52b83aad3b7834a0d Mon Sep 17 00:00:00 2001 From: Kris Zyp Date: Sat, 26 May 2012 22:11:04 -0600 Subject: [PATCH 6/6] Fix event test so it doesn't break without an event parameter --- OnDemandList.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OnDemandList.js b/OnDemandList.js index 1bc727cb5..56acfb2da 100644 --- a/OnDemandList.js +++ b/OnDemandList.js @@ -169,7 +169,7 @@ return declare([List, _StoreMixin], { var grid = this, scrollNode = grid.bodyNode, transform = grid.contentNode.style.webkitTransform, - visibleTop = event.pseudoTouch ? scrollNode.instantScrollTop : scrollNode.scrollTop, + visibleTop = event && event.pseudoTouch ? scrollNode.instantScrollTop : scrollNode.scrollTop, visibleBottom = scrollNode.offsetHeight + visibleTop, priorPreload, preloadNode, preload = grid.preload, lastScrollTop = grid.lastScrollTop,