diff --git a/experiments/scrollview/jquery.mobile.scrollview.css b/experiments/scrollview/jquery.mobile.scrollview.css index 9de89895..5112e431 100644 --- a/experiments/scrollview/jquery.mobile.scrollview.css +++ b/experiments/scrollview/jquery.mobile.scrollview.css @@ -1,21 +1,9 @@ @charset "utf-8"; .ui-scrollview-clip { - overflow: hidden; - position: relative; } .ui-scrollview-view { - position: relative; - overflow: hidden; - top: 0; - left: 0; -/* - min-width: 100%; - min-height: 100%; - padding: 0; - margin: 0; -*/ } .ui-scrolllistview .ui-li-divider { @@ -36,14 +24,14 @@ opacity: 1; } -.ui-scrollbar-vertical { +.ui-scrollbar-y { top: 2px; right: 2px; bottom: 8px; width: 5px; } -.ui-scrollbar-horizontal { +.ui-scrollbar-x { right: 8px; bottom: 2px; left: 2px; @@ -66,12 +54,12 @@ border-radius: 2px; } -.ui-scrollbar-vertical .ui-scrollbar-thumb { +.ui-scrollbar-y .ui-scrollbar-thumb { width: 5px; height: 100%; } -.ui-scrollbar-horizontal .ui-scrollbar-thumb { +.ui-scrollbar-x .ui-scrollbar-thumb { width: 100%; height: 5px; } diff --git a/experiments/scrollview/jquery.mobile.scrollview.js b/experiments/scrollview/jquery.mobile.scrollview.js index faa241cf..5420c5a0 100644 --- a/experiments/scrollview/jquery.mobile.scrollview.js +++ b/experiments/scrollview/jquery.mobile.scrollview.js @@ -9,25 +9,32 @@ jQuery.widget( "mobile.scrollview", jQuery.mobile.widget, { options: { fps: 60, // Frames per second in msecs. - direction: null, // "vertical", "horizontal", or null for both. + direction: null, // "x", "y", or null for both. scrollDuration: 2000, // Duration of the scrolling animation in msecs. overshootDuration: 250, // Duration of the overshoot animation in msecs. snapbackDuration: 500, // Duration of the snapback animation in msecs. - moveThreshold: 100, // Time between mousemoves must not exceed this threshold. + moveThreshold: 10, // User must move this many pixels in any direction to trigger a scroll. + moveIntervalThreshold: 100, // Time between mousemoves must not exceed this threshold. useCSSTransform: true, // Use CSS "transform" property instead of "top" and "left" for positioning. - startEventName: "scrollstart.scrollview", - updateEventName: "scrollupdate.scrollview", - stopEventName: "scrollstop.scrollview", + startEventName: "scrollstart", + updateEventName: "scrollupdate", + stopEventName: "scrollstop", eventType: $.support.touch ? "touch" : "mouse", showScrollBars: true }, + _makePositioned: function($ele) + { + if ($ele.css("position") == "static") + $ele.css("position", "relative"); + }, + _create: function() { this._$clip = $(this.element).addClass("ui-scrollview-clip"); @@ -36,13 +43,24 @@ jQuery.widget( "mobile.scrollview", jQuery.mobile.widget, { $child = this._$clip.wrapInner("
").children(); } this._$view = $child.addClass("ui-scrollview-view"); - + + this._$clip.css("overflow", "hidden"); + this._makePositioned(this._$clip); + + this._$view.css("overflow", "hidden"); + + if (!this.options.useCSSTransform) + { + this._makePositioned(this._$view); + this._$view.css({ left: 0, top: 0 }); + } + this._sx = 0; this._sy = 0; var direction = this.options.direction; - this._hTracker = (direction != "vertical") ? new MomentumTracker(this.options) : null; - this._vTracker = (direction != "horizontal") ? new MomentumTracker(this.options) : null; + this._hTracker = (direction != "y") ? new MomentumTracker(this.options) : null; + this._vTracker = (direction != "x") ? new MomentumTracker(this.options) : null; this._timerInterval = 1000/this.options.fps; this._timerID = 0; @@ -137,27 +155,21 @@ jQuery.widget( "mobile.scrollview", jQuery.mobile.widget, { this._stopMScroll(); }, - _setElementTransform: function($ele, x, y) - { - var v = "translate3d(" + x + "," + y + ", 0px)"; - $ele.css({ - "-moz-transform": v, - "-webkit-transform": v, - "transform": v - }); - }, - _setScrollPosition: function(x, y) { this._sx = x; this._sy = y; + var kdebug = 0; + if (y == 0) + ++kdebug; + var $v = this._$view; var uct = this.options.useCSSTransform; if (uct) - this._setElementTransform($v, x + "px", y + "px"); + setElementTransform($v, x + "px", y + "px"); else $v.css({left: x + "px", top: y + "px"}); @@ -170,7 +182,7 @@ jQuery.widget( "mobile.scrollview", jQuery.mobile.widget, { { var $sbt = $vsb.find(".ui-scrollbar-thumb"); if (uct) - this._setElementTransform($sbt, "0px", -y/$v.height() * $sbt.parent().height() + "px"); + setElementTransform($sbt, "0px", -y/$v.height() * $sbt.parent().height() + "px"); else $sbt.css("top", -y/$v.height()*100 + "%"); } @@ -179,29 +191,94 @@ jQuery.widget( "mobile.scrollview", jQuery.mobile.widget, { { var $sbt = $hsb.find(".ui-scrollbar-thumb"); if (uct) - this._setElementTransform($sbt, -x/$v.width() * $sbt.parent().width() + "px", "0px"); + setElementTransform($sbt, -x/$v.width() * $sbt.parent().width() + "px", "0px"); else $sbt.css("left", -x/$v.width()*100 + "%"); } } }, + scrollTo: function(x, y, duration) + { + this._stopMScroll(); + if (!duration) + return this._setScrollPosition(x, y); + + x = -x; + y = -y; + + var self = this; + var start = getCurrentTime(); + var efunc = $.easing["easeOutQuad"]; + var sx = this._sx; + var sy = this._sy; + var dx = x - sx; + var dy = y - sy; + var tfunc = function(){ + var elapsed = getCurrentTime() - start; + if (elapsed >= duration) + { + self._timerID = 0; + self._setScrollPosition(x, y); + } + else + { + var ec = efunc(elapsed/duration, elapsed, 0, 1, duration); + self._setScrollPosition(sx + (dx * ec), sy + (dy * ec)); + self._timerID = setTimeout(tfunc, self._timerInterval); + } + }; + + this._timerID = setTimeout(tfunc, this._timerInterval); + }, + _getScrollPosition: function(x, y) { return { x: this._sx, y: this._sy }; }, - _handleMouseDown: function(e, ex, ey) + _getScrollHierarchy: function() { + var svh = []; + this._$clip.parents(".ui-scrollview-clip").each(function(){ + var d = $(this).data("scrollview"); + if (d) svh.unshift(d); + }); + return svh; + }, + + _getAncestorByDirection: function(dir) + { + var svh = this._getScrollHierarchy(); + var n = svh.length; + while (0 < n--) + { + var sv = svh[n]; + var svdir = sv.options.direction; + + if (!svdir || svdir == dir) + return sv; + } + return null; + }, + + _handleDragStart: function(e, ex, ey) + { + // Stop any scrolling of elements in our parent hierarcy. + $.each(this._getScrollHierarchy(),function(i,sv){ sv._stopMScroll(); }); this._stopMScroll(); var c = this._$clip; var v = this._$view; + this._lastX = ex; + this._lastY = ey; this._doSnapBackX = false; this._doSnapBackY = false; this._speedX = 0; this._speedY = 0; + this._directionLock = ""; + this._didDrag = false; if (this._hTracker) { @@ -209,7 +286,6 @@ jQuery.widget( "mobile.scrollview", jQuery.mobile.widget, { var vw = parseInt(v.css("width"), 10); this._maxX = cw - vw; if (this._maxX > 0) this._maxX = 0; - this._lastX = ex; if (this._$hScrollBar) this._$hScrollBar.find(".ui-scrollbar-thumb").css("width", (cw >= vw ? "100%" : Math.floor(cw/vw*100)+ "%")); } @@ -220,7 +296,6 @@ jQuery.widget( "mobile.scrollview", jQuery.mobile.widget, { var vh = parseInt(v.css("height"), 10); this._maxY = ch - vh; if (this._maxY > 0) this._maxY = 0; - this._lastY = ey; if (this._$vScrollBar) this._$vScrollBar.find(".ui-scrollbar-thumb").css("height", (ch >= vh ? "100%" : Math.floor(ch/vh*100)+ "%")); } @@ -237,20 +312,69 @@ jQuery.widget( "mobile.scrollview", jQuery.mobile.widget, { if (this.options.eventType == "mouse") e.preventDefault(); + e.stopPropagation(); }, - _handleMouseMove: function(e, ex, ey) + _propagateDragMove: function(sv, e, ex, ey, dir) + { + this._hideScrollBars(); + this._disableTracking(); + sv._handleDragStart(e,ex,ey); + sv._directionLock = dir; + sv._didDrag = this._didDrag; + }, + + _handleDragMove: function(e, ex, ey) { this._lastMove = getCurrentTime(); var v = this._$view; - var newX = 0; - var newY = 0; + var dx = ex - this._lastX; + var dy = ey - this._lastY; - if (this._hTracker) + if (!this._directionLock) + { + var x = Math.abs(dx); + var y = Math.abs(dy); + var mt = this.options.moveThreshold; + + if (x < mt && y < mt) { + return false; + } + + var dir = null; + var r = 0; + if (x < y && (x/y) < 0.5) { + dir = "y"; + } + else if (x > y && (y/x) < 0.5) { + dir = "x"; + } + + var svdir = this.options.direction; + if (svdir && dir && svdir != dir) + { + // This scrollview can't handle the direction the user + // is attempting to scroll. Find an ancestor scrollview + // that can handle the request. + + var sv = this._getAncestorByDirection(dir); + if (sv) + { + this._propagateDragMove(sv, e, ex, ey, dir); + return false; + } + } + + this._directionLock = svdir ? svdir : (dir ? dir : "none"); + } + + var newX = this._sx; + var newY = this._sy; + + if (this._directionLock != "y" && this._hTracker) { - var dx = ex - this._lastX; var x = this._sx; this._speedX = dx; newX = x + dx; @@ -260,16 +384,23 @@ jQuery.widget( "mobile.scrollview", jQuery.mobile.widget, { this._doSnapBackX = false; if (newX > 0 || newX < this._maxX) { + if (this._directionLock == "x") + { + var sv = this._getAncestorByDirection("x"); + if (sv) + { + this._setScrollPosition(newX > 0 ? 0 : this._maxX, newY); + this._propagateDragMove(sv, e, ex, ey, dir); + return false; + } + } newX = x + (dx/2); this._doSnapBackX = true; } - - this._lastX = ex; } - if (this._vTracker) + if (this._directionLock != "x" && this._vTracker) { - var dy = ey - this._lastY; var y = this._sy; this._speedY = dy; newY = y + dy; @@ -279,13 +410,27 @@ jQuery.widget( "mobile.scrollview", jQuery.mobile.widget, { this._doSnapBackY = false; if (newY > 0 || newY < this._maxY) { + if (this._directionLock == "y") + { + var sv = this._getAncestorByDirection("y"); + if (sv) + { + this._setScrollPosition(newX, newY > 0 ? 0 : this._maxY); + this._propagateDragMove(sv, e, ex, ey, dir); + return false; + } + } + newY = y + (dy/2); this._doSnapBackY = true; } - this._lastY = ey; } + this._didDrag = true; + this._lastX = ex; + this._lastY = ey; + this._setScrollPosition(newX, newY); this._showScrollBars(); @@ -293,39 +438,44 @@ jQuery.widget( "mobile.scrollview", jQuery.mobile.widget, { // Call preventDefault() to prevent touch devices from // scrolling the main window. - e.preventDefault(); + // e.preventDefault(); + + return false; }, - _handleMouseUp: function(e) + _handleDragStop: function(e) { var l = this._lastMove; var t = getCurrentTime(); - var doScroll = l && (t - l) <= this.options.moveThreshold; + var doScroll = l && (t - l) <= this.options.moveIntervalThreshold; var sx = (this._hTracker && this._speedX && doScroll) ? this._speedX : (this._doSnapBackX ? 1 : 0); var sy = (this._vTracker && this._speedY && doScroll) ? this._speedY : (this._doSnapBackY ? 1 : 0); if (sx || sy) - { this._startMScroll(sx, sy); - e.preventDefault(); - } else this._hideScrollBars(); this._disableTracking(); + + // If a view scrolled, then we need to absorb + // the event so that links etc, underneath our + // cursor/finger don't fire. + + return this._didDrag ? false : undefined; }, _enableTracking: function() { - $(document).bind(this._mousemoveType, this._mousemoveCB); - $(document).bind(this._mouseupType, this._mouseupCB); + $(document).bind(this._dragMoveEvt, this._dragMoveCB); + $(document).bind(this._dragStopEvt, this._dragStopCB); }, _disableTracking: function() { - $(document).unbind(this._mousemoveType, this._mousemoveCB); - $(document).unbind(this._mouseupType, this._mouseupCB); + $(document).unbind(this._dragMoveEvt, this._dragMoveCB); + $(document).unbind(this._dragStopEvt, this._dragStopCB); }, _showScrollBars: function() @@ -347,36 +497,36 @@ jQuery.widget( "mobile.scrollview", jQuery.mobile.widget, { var self = this; if (this.options.eventType == "mouse") { - this._mousedownType = "mousedown"; - this._mousedownCB = function(e){ return self._handleMouseDown(e, e.clientX, e.clientY); }; + this._dragStartEvt = "mousedown"; + this._dragStartCB = function(e){ return self._handleDragStart(e, e.clientX, e.clientY); }; - this._mousemoveType = "mousemove"; - this._mousemoveCB = function(e){ return self._handleMouseMove(e, e.clientX, e.clientY); }; + this._dragMoveEvt = "mousemove"; + this._dragMoveCB = function(e){ return self._handleDragMove(e, e.clientX, e.clientY); }; - this._mouseupType = "mouseup"; - this._mouseupCB = function(e){ return self._handleMouseUp(e); }; + this._dragStopEvt = "mouseup"; + this._dragStopCB = function(e){ return self._handleDragStop(e); }; } else // "touch" { - this._mousedownType = "touchstart"; - this._mousedownCB = function(e) + this._dragStartEvt = "touchstart"; + this._dragStartCB = function(e) { var t = e.originalEvent.targetTouches[0]; - return self._handleMouseDown(e, t.pageX, t.pageY); + return self._handleDragStart(e, t.pageX, t.pageY); }; - this._mousemoveType = "touchmove"; - this._mousemoveCB = function(e) + this._dragMoveEvt = "touchmove"; + this._dragMoveCB = function(e) { var t = e.originalEvent.targetTouches[0]; - return self._handleMouseMove(e, t.pageX, t.pageY); + return self._handleDragMove(e, t.pageX, t.pageY); }; - this._mouseupType = "touchend"; - this._mouseupCB = function(e){ return self._handleMouseUp(e); }; + this._dragStopEvt = "touchend"; + this._dragStopCB = function(e){ return self._handleDragStop(e); }; } - this._$view.bind(this._mousedownType, this._mousedownCB); + this._$view.bind(this._dragStartEvt, this._dragStartCB); if (this.options.showScrollBars) { @@ -385,18 +535,29 @@ jQuery.widget( "mobile.scrollview", jQuery.mobile.widget, { var suffix = "\">"; if (this._vTracker) { - $c.append(prefix + "vertical" + suffix); - this._$vScrollBar = $c.children(".ui-scrollbar-vertical"); + $c.append(prefix + "y" + suffix); + this._$vScrollBar = $c.children(".ui-scrollbar-y"); } if (this._hTracker) { - $c.append(prefix + "horizontal" + suffix); - this._$hScrollBar = $c.children(".ui-scrollbar-horizontal"); + $c.append(prefix + "x" + suffix); + this._$hScrollBar = $c.children(".ui-scrollbar-x"); } } } }); +function setElementTransform($ele, x, y) +{ + var v = "translate3d(" + x + "," + y + ", 0px)"; + $ele.css({ + "-moz-transform": v, + "-webkit-transform": v, + "transform": v + }); +} + + function MomentumTracker(options) { this.options = $.extend({}, options); @@ -506,7 +667,7 @@ $.extend(MomentumTracker.prototype, { jQuery.widget( "mobile.scrolllistview", jQuery.mobile.scrollview, { options: { - direction: "vertical" + direction: "y" }, _create: function() { @@ -566,15 +727,10 @@ jQuery.widget( "mobile.scrolllistview", jQuery.mobile.scrollview, { // XXX: Need to convert this over to using $().css() and supporting the non-transform case. var ld = this._lastDivider; - if (ld && d != ld) - { - var zt = "translate3d(0px,0px,0px)"; - // $(ld).css("-webkit-transform", zt).css("-moz-transform", zt).css("transform", zt); - ld.style.webkitTransform = zt; ld.style.MozTransform = zt; ld.style.transform = zt; + if (ld && d != ld) { + setElementTransform($(ld), 0, 0); } - var str = "translate3d(0px," + y + "px,0px)"; - // $(d).css("-webkit-transform", str).css("-moz-transform", str).css("transform", str); - d.style.webkitTransform = str; d.style.MozTransform = str; d.style.transform = str; + setElementTransform($(d), 0, y + "px"); this._lastDivider = d; } diff --git a/experiments/scrollview/scrollview-direction.html b/experiments/scrollview/scrollview-direction.html new file mode 100644 index 00000000..d26ed1f7 --- /dev/null +++ b/experiments/scrollview/scrollview-direction.html @@ -0,0 +1,786 @@ + + + + +To turn an element into a scrollview, simply add a data-scroll="true" to the element. By default, a scrollview can scroll in both the horizontal and vertical directions. If the user drags the view horizontally (left or right), or vertically (up or down), scrolling will be locked so that it only scrolls in that one dimension. If the user drags the view diagonally, he will be able to scroll in both directions at the same time.
+When there are nested scrollviews, if the user drags in a single dimension and hits either end of the view, the drag will be propagated up to the next outer scrollview that can handle a drag in that dimension. So for example, if you drag the scrollview above so that it reaches the top of its view, the entire page will start to scroll upward if you continue dragging. This is because the drag was propagated from the scrollview with the letters in it, out to the scrollview containing the entire content for the page.
+A scrollview can be set up so that it only scrolls in the horizontal direction. Simply place a data-scroll="x" on the element you want to scroll:
+A scrollview can be set up so that it only scrolls in the vertical direction. Simply place a data-scroll="y" on the element you want to scroll:
+A scrollview can be set up so that it scrolls by pages. This feature is only enabled for horizontal or vertical scrollviews.
+Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui.Donec non enim in turpis pulvinar facilisis. Ut felis.
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui.Donec non enim in turpis pulvinar facilisis. Ut felis.
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui.Donec non enim in turpis pulvinar facilisis. Ut felis.
+ +
In the following example the #4 is actually a vertical scrollview embedded within a horizontal scrollview.
+In the following example the #4 is actually a nested horizontal scrollview embedded within a horizontal scrollview. The idea here is that if you drag-scroll the nested scrollview, once it reaches either end of its view, it should start scrolling the outer view.
+Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui.Donec non enim in turpis pulvinar facilisis. Ut felis.
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui.Donec non enim in turpis pulvinar facilisis. Ut felis.
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. Aenean ultricies mi vitae est. Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, commodo vitae, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui.Donec non enim in turpis pulvinar facilisis. Ut felis.
+ +