Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial checkin of experimental support for momentum scrolling. We'd …

…really like native browser support for sub-page scrolling, but until then, we need something.

This version supports simulated scrolling via the CSS3 transform property by default, but also supports an option for positioning the old-fashioned way with top and left properties.

Still some code clean-up to do, but folks can start playing with it.

Some items/issues left to look into:

- Experiment with event delegation so we can implement nested scrollviews.
- Implement scroll direction locking.
- Decide whether to conditionally inject clip/view markup.
- Decide on final ui class names.
- Decide on how scrolling behavior is actually invoked. (data-* attribute or class)
- Decide on final set of notifications we'll need to fire off.
- Add an API so that scroll position can be adjusted after a resize/orientation change.
- Documentation that describes potential problems with performance, memory usage, etc, and workarounds.
  • Loading branch information...
commit dbdfd23541924bf14142f985ef5093789f4a460f 1 parent 42d20b4
@jblas jblas authored
View
57 experiments/scrollview/index.html
@@ -0,0 +1,57 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <meta charset="utf-8" />
+ <title>jQuery Mobile: Demos and Documentation</title>
+ <link rel="stylesheet" href="../../themes/default" />
+ <link rel="stylesheet" href="jquery.mobile.scrollview.css" />
+ <link rel="stylesheet" href="../../docs/_assets/css/jqm-docs.css" />
+ <style type="text/css">
+ .ui-content.ui-scrollview-clip {
+ padding: 0;
+ }
+ .ui-content.ui-scrollview-clip > div.ui-scrollview-view {
+ margin: 0;
+ padding: 15px;
+ }
+ .ui-content.ui-scrollview-clip > .ui-listview.ui-scrollview-view {
+ margin: 0;
+ }
+ </style>
+ <script src="../../js"></script>
+ <script src="jquery.easing.1.3.js"></script>
+ <script src="jquery.mobile.scrollview.js"></script>
+ <script src="scrollview.js"></script>
+ <script src="../themeswitcher/jquery.mobile.themeswitcher.js"></script>
+ <script src="../../docs/_assets/js/jqm-docs.js"></script>
+</head>
+<body>
+<div data-role="page" data-theme="b" id="jqm-home">
+ <div id="jqm-homeheader">
+ <h1 id="jqm-logo"><img src="../../docs/_assets/images/jquery-logo.png" alt="jQuery Mobile Framework" width="235" height="61" /></h1>
+ <p>A few examples tweaked to make use of the scrollview component.</p>
+ <p id="jqm-version">Alpha Release</p>
+ </div>
+
+ <div data-role="content">
+
+ <ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="b">
+ <li data-role="list-divider">Toolbars</li>
+ <li><a href="../../docs/toolbars/footer-persist-a.html">Persistent footer nav bar (a)</a></li>
+ <li><a href="../../docs/toolbars/footer-persist-b.html">Persistent footer nav bar (b)</a></li>
+ <li><a href="../../docs/toolbars/footer-persist-c.html">Persistent footer nav bar (c)</a></li>
+ </ul>
+
+ <ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="b">
+ <li data-role="list-divider">List Views</li>
+ <li><a href="lists-divider.html">Sticky list dividers</a></li>
+ </ul>
+
+ <ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="b">
+ <li data-role="list-divider">Forms</li>
+ <li><a href="../../docs/forms/forms-all.html">Form element gallery</a></li>
+ </ul>
+ </div>
+</div>
+</body>
+</html>
View
205 experiments/scrollview/jquery.easing.1.3.js
@@ -0,0 +1,205 @@
+/*
+ * jQuery Easing v1.3 - http://gsgd.co.uk/sandbox/jquery/easing/
+ *
+ * Uses the built in easing capabilities added In jQuery 1.1
+ * to offer multiple easing options
+ *
+ * TERMS OF USE - jQuery Easing
+ *
+ * Open source under the BSD License.
+ *
+ * Copyright © 2008 George McGinley Smith
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * Neither the name of the author nor the names of contributors may be used to endorse
+ * or promote products derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+*/
+
+// t: current time, b: begInnIng value, c: change In value, d: duration
+jQuery.easing['jswing'] = jQuery.easing['swing'];
+
+jQuery.extend( jQuery.easing,
+{
+ def: 'easeOutQuad',
+ swing: function (x, t, b, c, d) {
+ //alert(jQuery.easing.default);
+ return jQuery.easing[jQuery.easing.def](x, t, b, c, d);
+ },
+ easeInQuad: function (x, t, b, c, d) {
+ return c*(t/=d)*t + b;
+ },
+ easeOutQuad: function (x, t, b, c, d) {
+ return -c *(t/=d)*(t-2) + b;
+ },
+ easeInOutQuad: function (x, t, b, c, d) {
+ if ((t/=d/2) < 1) return c/2*t*t + b;
+ return -c/2 * ((--t)*(t-2) - 1) + b;
+ },
+ easeInCubic: function (x, t, b, c, d) {
+ return c*(t/=d)*t*t + b;
+ },
+ easeOutCubic: function (x, t, b, c, d) {
+ return c*((t=t/d-1)*t*t + 1) + b;
+ },
+ easeInOutCubic: function (x, t, b, c, d) {
+ if ((t/=d/2) < 1) return c/2*t*t*t + b;
+ return c/2*((t-=2)*t*t + 2) + b;
+ },
+ easeInQuart: function (x, t, b, c, d) {
+ return c*(t/=d)*t*t*t + b;
+ },
+ easeOutQuart: function (x, t, b, c, d) {
+ return -c * ((t=t/d-1)*t*t*t - 1) + b;
+ },
+ easeInOutQuart: function (x, t, b, c, d) {
+ if ((t/=d/2) < 1) return c/2*t*t*t*t + b;
+ return -c/2 * ((t-=2)*t*t*t - 2) + b;
+ },
+ easeInQuint: function (x, t, b, c, d) {
+ return c*(t/=d)*t*t*t*t + b;
+ },
+ easeOutQuint: function (x, t, b, c, d) {
+ return c*((t=t/d-1)*t*t*t*t + 1) + b;
+ },
+ easeInOutQuint: function (x, t, b, c, d) {
+ if ((t/=d/2) < 1) return c/2*t*t*t*t*t + b;
+ return c/2*((t-=2)*t*t*t*t + 2) + b;
+ },
+ easeInSine: function (x, t, b, c, d) {
+ return -c * Math.cos(t/d * (Math.PI/2)) + c + b;
+ },
+ easeOutSine: function (x, t, b, c, d) {
+ return c * Math.sin(t/d * (Math.PI/2)) + b;
+ },
+ easeInOutSine: function (x, t, b, c, d) {
+ return -c/2 * (Math.cos(Math.PI*t/d) - 1) + b;
+ },
+ easeInExpo: function (x, t, b, c, d) {
+ return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
+ },
+ easeOutExpo: function (x, t, b, c, d) {
+ return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
+ },
+ easeInOutExpo: function (x, t, b, c, d) {
+ if (t==0) return b;
+ if (t==d) return b+c;
+ if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
+ return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
+ },
+ easeInCirc: function (x, t, b, c, d) {
+ return -c * (Math.sqrt(1 - (t/=d)*t) - 1) + b;
+ },
+ easeOutCirc: function (x, t, b, c, d) {
+ return c * Math.sqrt(1 - (t=t/d-1)*t) + b;
+ },
+ easeInOutCirc: function (x, t, b, c, d) {
+ if ((t/=d/2) < 1) return -c/2 * (Math.sqrt(1 - t*t) - 1) + b;
+ return c/2 * (Math.sqrt(1 - (t-=2)*t) + 1) + b;
+ },
+ easeInElastic: function (x, t, b, c, d) {
+ var s=1.70158;var p=0;var a=c;
+ if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
+ if (a < Math.abs(c)) { a=c; var s=p/4; }
+ else var s = p/(2*Math.PI) * Math.asin (c/a);
+ return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
+ },
+ easeOutElastic: function (x, t, b, c, d) {
+ var s=1.70158;var p=0;var a=c;
+ if (t==0) return b; if ((t/=d)==1) return b+c; if (!p) p=d*.3;
+ if (a < Math.abs(c)) { a=c; var s=p/4; }
+ else var s = p/(2*Math.PI) * Math.asin (c/a);
+ return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
+ },
+ easeInOutElastic: function (x, t, b, c, d) {
+ var s=1.70158;var p=0;var a=c;
+ if (t==0) return b; if ((t/=d/2)==2) return b+c; if (!p) p=d*(.3*1.5);
+ if (a < Math.abs(c)) { a=c; var s=p/4; }
+ else var s = p/(2*Math.PI) * Math.asin (c/a);
+ if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )) + b;
+ return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*d-s)*(2*Math.PI)/p )*.5 + c + b;
+ },
+ easeInBack: function (x, t, b, c, d, s) {
+ if (s == undefined) s = 1.70158;
+ return c*(t/=d)*t*((s+1)*t - s) + b;
+ },
+ easeOutBack: function (x, t, b, c, d, s) {
+ if (s == undefined) s = 1.70158;
+ return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
+ },
+ easeInOutBack: function (x, t, b, c, d, s) {
+ if (s == undefined) s = 1.70158;
+ if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
+ return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
+ },
+ easeInBounce: function (x, t, b, c, d) {
+ return c - jQuery.easing.easeOutBounce (x, d-t, 0, c, d) + b;
+ },
+ easeOutBounce: function (x, t, b, c, d) {
+ if ((t/=d) < (1/2.75)) {
+ return c*(7.5625*t*t) + b;
+ } else if (t < (2/2.75)) {
+ return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
+ } else if (t < (2.5/2.75)) {
+ return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
+ } else {
+ return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
+ }
+ },
+ easeInOutBounce: function (x, t, b, c, d) {
+ if (t < d/2) return jQuery.easing.easeInBounce (x, t*2, 0, c, d) * .5 + b;
+ return jQuery.easing.easeOutBounce (x, t*2-d, 0, c, d) * .5 + c*.5 + b;
+ }
+});
+
+/*
+ *
+ * TERMS OF USE - EASING EQUATIONS
+ *
+ * Open source under the BSD License.
+ *
+ * Copyright © 2001 Robert Penner
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * Redistributions of source code must retain the above copyright notice, this list of
+ * conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, this list
+ * of conditions and the following disclaimer in the documentation and/or other materials
+ * provided with the distribution.
+ *
+ * Neither the name of the author nor the names of contributors may be used to endorse
+ * or promote products derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+ * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ *
+ */
View
77 experiments/scrollview/jquery.mobile.scrollview.css
@@ -0,0 +1,77 @@
+@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 {
+ z-index: 10;
+}
+
+.ui-scrollbar {
+ position: absolute;
+ overflow: hidden;
+
+ opacity: 0;
+ -webkit-transition: opacity 500ms;
+ -moz-transition: opacity 500ms;
+ transition: opacity 500ms;
+}
+
+.ui-scrollbar-visible {
+ opacity: 1;
+}
+
+.ui-scrollbar-vertical {
+ top: 2px;
+ right: 2px;
+ bottom: 8px;
+ width: 5px;
+}
+
+.ui-scrollbar-horizontal {
+ right: 8px;
+ bottom: 2px;
+ left: 2px;
+ height: 5px;
+}
+
+.ui-scrollbar-track {
+ position: relative;
+ width: 100%;
+ height: 100%;
+}
+
+.ui-scrollbar-thumb {
+ position: absolute;
+ top: 0;
+ left: 0;
+ background-color: rgba(0, 0, 0, 0.3);
+ -moz-border-radius: 2px;
+ -webkit-border-radius: 2px;
+ border-radius: 2px;
+}
+
+.ui-scrollbar-vertical .ui-scrollbar-thumb {
+ width: 5px;
+ height: 100%;
+}
+
+.ui-scrollbar-horizontal .ui-scrollbar-thumb {
+ width: 100%;
+ height: 5px;
+}
View
584 experiments/scrollview/jquery.mobile.scrollview.js
@@ -0,0 +1,584 @@
+/*
+* jQuery Mobile Framework : scrollview plugin
+* Copyright (c) 2010 Adobe Systems Incorporated - Kin Blas (jblas@adobe.com)
+* Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses.
+* Note: Code is in draft form and is subject to change
+*/
+(function($,window,document,undefined){
+
+jQuery.widget( "mobile.scrollview", jQuery.mobile.widget, {
+ options: {
+ fps: 60, // Frames per second in msecs.
+ direction: null, // "vertical", "horizontal", 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.
+
+ useCSSTransform: true, // Use CSS "transform" property instead of "top" and "left" for positioning.
+
+ startEventName: "scrollstart.scrollview",
+ updateEventName: "scrollupdate.scrollview",
+ stopEventName: "scrollstop.scrollview",
+
+ eventType: $.support.touch ? "touch" : "mouse",
+
+ showScrollBars: true
+ },
+
+ _create: function()
+ {
+ this._$clip = $(this.element).addClass("ui-scrollview-clip");
+ var $child = this._$clip.children();
+ if ($child.length > 1) {
+ $child = this._$clip.wrapInner("<div></div>").children();
+ }
+ this._$view = $child.addClass("ui-scrollview-view");
+
+ 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._timerInterval = 1000/this.options.fps;
+ this._timerID = 0;
+
+ var self = this;
+ this._timerCB = function(){ self._handleMomentumScroll(); };
+
+ this._addBehaviors();
+ },
+
+ _startMScroll: function(speedX, speedY)
+ {
+ this._stopMScroll();
+ this._showScrollBars();
+
+ var keepGoing = false;
+ var duration = this.options.scrollDuration;
+
+ this._$clip.trigger(this.options.startEventName);
+
+ var ht = this._hTracker;
+ if (ht)
+ {
+ var c = this._$clip.width();
+ var v = this._$view.width();
+ ht.start(this._sx, speedX, duration, (v > c) ? -(v - c) : 0, 0);
+ keepGoing = !ht.done();
+ }
+
+ var vt = this._vTracker;
+ if (vt)
+ {
+ var c = this._$clip.height();
+ var v = this._$view.height();
+ vt.start(this._sy, speedY, duration, (v > c) ? -(v - c) : 0, 0);
+ keepGoing = keepGoing || !vt.done();
+ }
+
+ if (keepGoing)
+ this._timerID = setTimeout(this._timerCB, this._timerInterval);
+ else
+ this._stopMScroll();
+ },
+
+ _stopMScroll: function()
+ {
+ if (this._timerID)
+ {
+ this._$clip.trigger(this.options.stopEventName);
+ clearTimeout(this._timerID);
+ }
+ this._timerID = 0;
+
+ if (this._vTracker)
+ this._vTracker.reset();
+
+ if (this._hTracker)
+ this._hTracker.reset();
+
+ this._hideScrollBars();
+ },
+
+ _handleMomentumScroll: function()
+ {
+ var keepGoing = false;
+ var v = this._$view;
+
+ var x = 0, y = 0;
+
+ var vt = this._vTracker;
+ if (vt)
+ {
+ vt.update();
+ y = vt.getPosition();
+ keepGoing = !vt.done();
+ }
+
+ var ht = this._hTracker;
+ if (ht)
+ {
+ ht.update();
+ x = ht.getPosition();
+ keepGoing = keepGoing || !ht.done();
+ }
+
+ this._setScrollPosition(x, y);
+ this._$clip.trigger(this.options.updateEventName, { x: x, y: y });
+
+ if (keepGoing)
+ this._timerID = setTimeout(this._timerCB, this._timerInterval);
+ else
+ 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 $v = this._$view;
+
+ var uct = this.options.useCSSTransform;
+
+ if (uct)
+ this._setElementTransform($v, x + "px", y + "px");
+ else
+ $v.css({left: x + "px", top: y + "px"});
+
+ var $vsb = this._$vScrollBar;
+ var $hsb = this._$hScrollBar;
+
+ if ($vsb || $hsb)
+ {
+ if ($vsb)
+ {
+ var $sbt = $vsb.find(".ui-scrollbar-thumb");
+ if (uct)
+ this._setElementTransform($sbt, "0px", -y/$v.height() * $sbt.parent().height() + "px");
+ else
+ $sbt.css("top", -y/$v.height()*100 + "%");
+ }
+
+ if ($hsb)
+ {
+ var $sbt = $hsb.find(".ui-scrollbar-thumb");
+ if (uct)
+ this._setElementTransform($sbt, -x/$v.width() * $sbt.parent().width() + "px", "0px");
+ else
+ $sbt.css("left", -x/$v.width()*100 + "%");
+ }
+ }
+ },
+
+ _getScrollPosition: function(x, y)
+ {
+ return { x: this._sx, y: this._sy };
+ },
+
+ _handleMouseDown: function(e, ex, ey)
+ {
+ this._stopMScroll();
+
+ var c = this._$clip;
+ var v = this._$view;
+
+ this._doSnapBackX = false;
+ this._doSnapBackY = false;
+ this._speedX = 0;
+ this._speedY = 0;
+
+ if (this._hTracker)
+ {
+ var cw = parseInt(c.css("width"), 10);
+ 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)+ "%"));
+ }
+
+ if (this._vTracker)
+ {
+ var ch = parseInt(c.css("height"), 10);
+ 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)+ "%"));
+ }
+
+ this._lastMove = 0;
+ this._enableTracking();
+
+ // If we're using mouse events, we need to prevent the default
+ // behavior to suppress accidental selection of text, etc. We
+ // can't do this on touch devices because it will disable the
+ // generation of "click" events.
+ //
+ // XXX: We should test if this has an effect on links! - kin
+
+ if (this.options.eventType == "mouse")
+ e.preventDefault();
+ },
+
+ _handleMouseMove: function(e, ex, ey)
+ {
+ this._lastMove = getCurrentTime();
+
+ var v = this._$view;
+
+ var newX = 0;
+ var newY = 0;
+
+ if (this._hTracker)
+ {
+ var dx = ex - this._lastX;
+ var x = this._sx;
+ this._speedX = dx;
+ newX = x + dx;
+
+ // Simulate resistance.
+
+ this._doSnapBackX = false;
+ if (newX > 0 || newX < this._maxX)
+ {
+ newX = x + (dx/2);
+ this._doSnapBackX = true;
+ }
+
+ this._lastX = ex;
+ }
+
+ if (this._vTracker)
+ {
+ var dy = ey - this._lastY;
+ var y = this._sy;
+ this._speedY = dy;
+ newY = y + dy;
+
+ // Simulate resistance.
+
+ this._doSnapBackY = false;
+ if (newY > 0 || newY < this._maxY)
+ {
+ newY = y + (dy/2);
+ this._doSnapBackY = true;
+ }
+
+ this._lastY = ey;
+ }
+
+ this._setScrollPosition(newX, newY);
+
+ this._showScrollBars();
+
+ // Call preventDefault() to prevent touch devices from
+ // scrolling the main window.
+
+ e.preventDefault();
+ },
+
+ _handleMouseUp: function(e)
+ {
+ var l = this._lastMove;
+ var t = getCurrentTime();
+ var doScroll = l && (t - l) <= this.options.moveThreshold;
+
+ 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();
+ },
+
+ _enableTracking: function()
+ {
+ $(document).bind(this._mousemoveType, this._mousemoveCB);
+ $(document).bind(this._mouseupType, this._mouseupCB);
+ },
+
+ _disableTracking: function()
+ {
+ $(document).unbind(this._mousemoveType, this._mousemoveCB);
+ $(document).unbind(this._mouseupType, this._mouseupCB);
+ },
+
+ _showScrollBars: function()
+ {
+ var vclass = "ui-scrollbar-visible";
+ if (this._$vScrollBar) this._$vScrollBar.addClass(vclass);
+ if (this._$hScrollBar) this._$hScrollBar.addClass(vclass);
+ },
+
+ _hideScrollBars: function()
+ {
+ var vclass = "ui-scrollbar-visible";
+ if (this._$vScrollBar) this._$vScrollBar.removeClass(vclass);
+ if (this._$hScrollBar) this._$hScrollBar.removeClass(vclass);
+ },
+
+ _addBehaviors: function()
+ {
+ var self = this;
+ if (this.options.eventType == "mouse")
+ {
+ this._mousedownType = "mousedown";
+ this._mousedownCB = function(e){ return self._handleMouseDown(e, e.clientX, e.clientY); };
+
+ this._mousemoveType = "mousemove";
+ this._mousemoveCB = function(e){ return self._handleMouseMove(e, e.clientX, e.clientY); };
+
+ this._mouseupType = "mouseup";
+ this._mouseupCB = function(e){ return self._handleMouseUp(e); };
+ }
+ else // "touch"
+ {
+ this._mousedownType = "touchstart";
+ this._mousedownCB = function(e)
+ {
+ var t = e.originalEvent.targetTouches[0];
+ return self._handleMouseDown(e, t.pageX, t.pageY);
+ };
+
+ this._mousemoveType = "touchmove";
+ this._mousemoveCB = function(e)
+ {
+ var t = e.originalEvent.targetTouches[0];
+ return self._handleMouseMove(e, t.pageX, t.pageY);
+ };
+
+ this._mouseupType = "touchend";
+ this._mouseupCB = function(e){ return self._handleMouseUp(e); };
+ }
+
+ this._$view.bind(this._mousedownType, this._mousedownCB);
+
+ if (this.options.showScrollBars)
+ {
+ var $c = this._$clip;
+ var prefix = "<div class=\"ui-scrollbar ui-scrollbar-";
+ var suffix = "\"><div class=\"ui-scrollbar-track\"><div class=\"ui-scrollbar-thumb\"></div></div></div>";
+ if (this._vTracker)
+ {
+ $c.append(prefix + "vertical" + suffix);
+ this._$vScrollBar = $c.children(".ui-scrollbar-vertical");
+ }
+ if (this._hTracker)
+ {
+ $c.append(prefix + "horizontal" + suffix);
+ this._$hScrollBar = $c.children(".ui-scrollbar-horizontal");
+ }
+ }
+ }
+});
+
+function MomentumTracker(options)
+{
+ this.options = $.extend({}, options);
+ this.easing = "easeOutQuad";
+ this.reset();
+}
+
+var tstates = {
+ scrolling: 0,
+ overshot: 1,
+ snapback: 2,
+ done: 3
+};
+
+function getCurrentTime() { return (new Date()).getTime(); }
+
+$.extend(MomentumTracker.prototype, {
+ start: function(pos, speed, duration, minPos, maxPos)
+ {
+ this.state = (speed != 0) ? ((pos < minPos || pos > maxPos) ? tstates.snapback : tstates.scrolling) : tstates.done;
+ this.pos = pos;
+ this.speed = speed;
+ this.duration = (this.state == tstates.snapback) ? this.options.snapbackDuration : duration;
+ this.minPos = minPos;
+ this.maxPos = maxPos;
+
+ this.fromPos = (this.state == tstates.snapback) ? this.pos : 0;
+ this.toPos = (this.state == tstates.snapback) ? ((this.pos < this.minPos) ? this.minPos : this.maxPos) : 0;
+
+ this.startTime = getCurrentTime();
+ },
+
+ reset: function()
+ {
+ this.state = tstates.done;
+ this.pos = 0;
+ this.speed = 0;
+ this.minPos = 0;
+ this.maxPos = 0;
+ this.duration = 0;
+ },
+
+ update: function()
+ {
+ var state = this.state;
+ if (state == tstates.done)
+ return this.pos;
+
+ var duration = this.duration;
+ var elapsed = getCurrentTime() - this.startTime;
+ elapsed = elapsed > duration ? duration : elapsed;
+
+ if (state == tstates.scrolling || state == tstates.overshot)
+ {
+ var dx = this.speed * (1 - $.easing[this.easing](elapsed/duration, elapsed, 0, 1, duration));
+
+ var x = this.pos + dx;
+
+ var didOverShoot = (state == tstates.scrolling) && (x < this.minPos || x > this.maxPos);
+ if (didOverShoot)
+ x = (x < this.minPos) ? this.minPos : this.maxPos;
+
+ this.pos = x;
+
+ if (state == tstates.overshot)
+ {
+ if (elapsed >= duration)
+ {
+ this.state = tstates.snapback;
+ this.fromPos = this.pos;
+ this.toPos = (x < this.minPos) ? this.minPos : this.maxPos;
+ this.duration = this.options.snapbackDuration;
+ this.startTime = getCurrentTime();
+ elapsed = 0;
+ }
+ }
+ else if (state == tstates.scrolling)
+ {
+ if (didOverShoot)
+ {
+ this.state = tstates.overshot;
+ this.speed = dx / 2;
+ this.duration = this.options.overshootDuration;
+ this.startTime = getCurrentTime();
+ }
+ else if (elapsed >= duration)
+ this.state = tstates.done;
+ }
+ }
+ else if (state == tstates.snapback)
+ {
+ if (elapsed >= duration)
+ {
+ this.pos = this.toPos;
+ this.state = tstates.done;
+ }
+ else
+ this.pos = this.fromPos + ((this.toPos - this.fromPos) * $.easing[this.easing](elapsed/duration, elapsed, 0, 1, duration));
+ }
+
+ return this.pos;
+ },
+
+ done: function() { return this.state == tstates.done; },
+ getPosition: function(){ return this.pos; }
+});
+
+jQuery.widget( "mobile.scrolllistview", jQuery.mobile.scrollview, {
+ options: {
+ direction: "vertical"
+ },
+
+ _create: function() {
+ $.mobile.scrollview.prototype._create.call(this);
+
+ // Cache the dividers so we don't have to search for them everytime the
+ // view is scrolled.
+ //
+ // XXX: Note that we need to update this cache if we ever support lists
+ // that can dynamically update their content.
+
+ this._$dividers = this._$view.find("[data-role=list-divider]");
+ this._lastDivider = null;
+ },
+
+ _setScrollPosition: function(x, y)
+ {
+ // Let the view scroll like it normally does.
+
+ $.mobile.scrollview.prototype._setScrollPosition.call(this, x, y);
+
+ y = -y;
+
+ // Find the dividers for the list.
+
+ var $divs = this._$dividers;
+ var cnt = $divs.length;
+ var d = null;
+ var dy = 0;
+ var nd = null;
+
+ for (var i = 0; i < cnt; i++)
+ {
+ nd = $divs.get(i);
+ var t = nd.offsetTop;
+ if (y >= t)
+ {
+ d = nd;
+ dy = t;
+ }
+ else if (d)
+ break;
+ }
+
+ // If we found a divider to move position it at the top of the
+ // clip view.
+
+ if (d)
+ {
+ var h = d.offsetHeight;
+ var mxy = (d != nd) ? nd.offsetTop : (this._$view.get(0).offsetHeight);
+ if (y + h >= mxy)
+ y = (mxy - h) - dy;
+ else
+ y = y - dy;
+
+ // 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;
+ }
+ 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;
+ this._lastDivider = d;
+
+ }
+ }
+});
+
+})(jQuery,window,document); // End Component
View
150 experiments/scrollview/lists-divider.html
@@ -0,0 +1,150 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <title>jQuery Mobile Docs - Lists</title>
+ <link rel="stylesheet" href="../../themes/default" />
+ <link rel="stylesheet" href="jquery.mobile.scrollview.css" />
+ <link rel="stylesheet" href="../../docs/_assets/css/jqm-docs.css"/>
+ <style type="text/css">
+ .ui-content.ui-scrollview-clip {
+ padding: 0;
+ }
+ .ui-content.ui-scrollview-clip > div.ui-scrollview-view {
+ margin: 0;
+ padding: 15px;
+ }
+ .ui-content.ui-scrollview-clip > .ui-listview.ui-scrollview-view {
+ margin: 0;
+ }
+ </style>
+ <script type="text/javascript" src="../../js"></script>
+ <script src="jquery.easing.1.3.js"></script>
+ <script src="jquery.mobile.scrollview.js"></script>
+ <script src="scrollview.js"></script>
+ <script type="text/javascript" src="../../docs/lists/docs/docs.js"></script>
+</head>
+<body>
+
+<div data-role="page">
+
+ <div data-role="header">
+ <h1>List dividers</h1>
+ </div><!-- /header -->
+
+ <div data-role="content" class="ui-scrolllistview">
+ <ul data-role="listview">
+ <li data-role="list-divider">A</li>
+ <li><a href="../../docs/lists/index.html">Adam Kinkaid</a></li>
+ <li><a href="../../docs/lists/index.html">Alex Wickerham</a></li>
+ <li><a href="../../docs/lists/index.html">Avery Johnson</a></li>
+ <li data-role="list-divider">B</li>
+ <li><a href="../../docs/lists/index.html">Bob Cabot</a></li>
+ <li data-role="list-divider">C</li>
+ <li><a href="../../docs/lists/index.html">Caleb Booth</a></li>
+ <li><a href="../../docs/lists/index.html">Christopher Adams</a></li>
+ <li><a href="../../docs/lists/index.html">Culver James</a></li>
+ <li data-role="list-divider">D</li>
+ <li><a href="../../docs/lists/index.html">David Walsh</a></li>
+ <li><a href="../../docs/lists/index.html">Drake Alfred</a></li>
+ <li data-role="list-divider">E</li>
+ <li><a href="../../docs/lists/index.html">Elizabeth Bacon</a></li>
+ <li><a href="../../docs/lists/index.html">Emery Parker</a></li>
+ <li><a href="../../docs/lists/index.html">Enid Voldon</a></li>
+ <li data-role="list-divider">F</li>
+ <li><a href="../../docs/lists/index.html">Francis Wall</a></li>
+ <li data-role="list-divider">G</li>
+ <li><a href="../../docs/lists/index.html">Graham Smith</a></li>
+ <li><a href="../../docs/lists/index.html">Greta Peete</a></li>
+ <li data-role="list-divider">H</li>
+ <li><a href="../../docs/lists/index.html">Harvey Walls</a></li>
+ <li data-role="list-divider">M</li>
+ <li><a href="../../docs/lists/index.html">Mike Farnsworth</a></li>
+ <li><a href="../../docs/lists/index.html">Murray Vanderbuilt</a></li>
+ <li data-role="list-divider">N</li>
+ <li><a href="../../docs/lists/index.html">Nathan Williams</a></li>
+ <li data-role="list-divider">P</li>
+ <li><a href="../../docs/lists/index.html">Paul Baker</a></li>
+ <li><a href="../../docs/lists/index.html">Pete Mason</a></li>
+ <li data-role="list-divider">R</li>
+ <li><a href="../../docs/lists/index.html">Rod Tarker</a></li>
+ <li data-role="list-divider">S</li>
+ <li><a href="../../docs/lists/index.html">Sawyer Wakefield</a></li>
+ <li data-role="list-divider">A</li>
+ <li><a href="../../docs/lists/index.html">Adam Kinkaid</a></li>
+ <li><a href="../../docs/lists/index.html">Alex Wickerham</a></li>
+ <li><a href="../../docs/lists/index.html">Avery Johnson</a></li>
+ <li data-role="list-divider">B</li>
+ <li><a href="../../docs/lists/index.html">Bob Cabot</a></li>
+ <li data-role="list-divider">C</li>
+ <li><a href="../../docs/lists/index.html">Caleb Booth</a></li>
+ <li><a href="../../docs/lists/index.html">Christopher Adams</a></li>
+ <li><a href="../../docs/lists/index.html">Culver James</a></li>
+ <li data-role="list-divider">D</li>
+ <li><a href="../../docs/lists/index.html">David Walsh</a></li>
+ <li><a href="../../docs/lists/index.html">Drake Alfred</a></li>
+ <li data-role="list-divider">E</li>
+ <li><a href="../../docs/lists/index.html">Elizabeth Bacon</a></li>
+ <li><a href="../../docs/lists/index.html">Emery Parker</a></li>
+ <li><a href="../../docs/lists/index.html">Enid Voldon</a></li>
+ <li data-role="list-divider">F</li>
+ <li><a href="../../docs/lists/index.html">Francis Wall</a></li>
+ <li data-role="list-divider">G</li>
+ <li><a href="../../docs/lists/index.html">Graham Smith</a></li>
+ <li><a href="../../docs/lists/index.html">Greta Peete</a></li>
+ <li data-role="list-divider">H</li>
+ <li><a href="../../docs/lists/index.html">Harvey Walls</a></li>
+ <li data-role="list-divider">M</li>
+ <li><a href="../../docs/lists/index.html">Mike Farnsworth</a></li>
+ <li><a href="../../docs/lists/index.html">Murray Vanderbuilt</a></li>
+ <li data-role="list-divider">N</li>
+ <li><a href="../../docs/lists/index.html">Nathan Williams</a></li>
+ <li data-role="list-divider">P</li>
+ <li><a href="../../docs/lists/index.html">Paul Baker</a></li>
+ <li><a href="../../docs/lists/index.html">Pete Mason</a></li>
+ <li data-role="list-divider">R</li>
+ <li><a href="../../docs/lists/index.html">Rod Tarker</a></li>
+ <li data-role="list-divider">S</li>
+ <li><a href="../../docs/lists/index.html">Sawyer Wakefield</a></li>
+ <li data-role="list-divider">A</li>
+ <li><a href="../../docs/lists/index.html">Adam Kinkaid</a></li>
+ <li><a href="../../docs/lists/index.html">Alex Wickerham</a></li>
+ <li><a href="../../docs/lists/index.html">Avery Johnson</a></li>
+ <li data-role="list-divider">B</li>
+ <li><a href="../../docs/lists/index.html">Bob Cabot</a></li>
+ <li data-role="list-divider">C</li>
+ <li><a href="../../docs/lists/index.html">Caleb Booth</a></li>
+ <li><a href="../../docs/lists/index.html">Christopher Adams</a></li>
+ <li><a href="../../docs/lists/index.html">Culver James</a></li>
+ <li data-role="list-divider">D</li>
+ <li><a href="../../docs/lists/index.html">David Walsh</a></li>
+ <li><a href="../../docs/lists/index.html">Drake Alfred</a></li>
+ <li data-role="list-divider">E</li>
+ <li><a href="../../docs/lists/index.html">Elizabeth Bacon</a></li>
+ <li><a href="../../docs/lists/index.html">Emery Parker</a></li>
+ <li><a href="../../docs/lists/index.html">Enid Voldon</a></li>
+ <li data-role="list-divider">F</li>
+ <li><a href="../../docs/lists/index.html">Francis Wall</a></li>
+ <li data-role="list-divider">G</li>
+ <li><a href="../../docs/lists/index.html">Graham Smith</a></li>
+ <li><a href="../../docs/lists/index.html">Greta Peete</a></li>
+ <li data-role="list-divider">H</li>
+ <li><a href="../../docs/lists/index.html">Harvey Walls</a></li>
+ <li data-role="list-divider">M</li>
+ <li><a href="../../docs/lists/index.html">Mike Farnsworth</a></li>
+ <li><a href="../../docs/lists/index.html">Murray Vanderbuilt</a></li>
+ <li data-role="list-divider">N</li>
+ <li><a href="../../docs/lists/index.html">Nathan Williams</a></li>
+ <li data-role="list-divider">P</li>
+ <li><a href="../../docs/lists/index.html">Paul Baker</a></li>
+ <li><a href="../../docs/lists/index.html">Pete Mason</a></li>
+ <li data-role="list-divider">R</li>
+ <li><a href="../../docs/lists/index.html">Rod Tarker</a></li>
+ <li data-role="list-divider">S</li>
+ <li><a href="../../docs/lists/index.html">Sawyer Wakefield</a></li>
+ </ul>
+ </div><!-- /content -->
+</div><!-- /page -->
+
+</body>
+</html>
View
22 experiments/scrollview/scrollview.js
@@ -0,0 +1,22 @@
+function ResizePageContentHeight(page)
+{
+ var $page = $(page);
+ var $content = $page.children(".ui-content");
+ var hh = $page.children(".ui-header").outerHeight(); hh = hh ? hh : 0;
+ var fh = $page.children(".ui-footer").outerHeight(); fh = fh ? fh : 0;
+ var pt = parseFloat($content.css("padding-top"));
+ var pb = parseFloat($content.css("padding-bottom"));
+ var wh = window.innerHeight;
+ $content.height(wh - (hh + fh) - (pt + pb));
+
+ $page.children(".ui-content:not(.ui-scrollview-clip):not(.ui-scrolllistview)").scrollview({ direction: "vertical" });
+ $page.children(".ui-content.ui-scrolllistview:not(.ui-scrollview-clip)").scrolllistview();
+}
+
+$("[data-role=page]").live("pageshow", function(event) {
+ ResizePageContentHeight(event.target);
+});
+
+$(document).live("orientationchange", function(event) {
+ ResizePageContentHeight($(".ui-page"));
+});
Please sign in to comment.
Something went wrong with that request. Please try again.