};
function getCurrentTime() {
- return ( new Date() ).getTime();
+ return Date.now();
}
jQuery.widget( "tizen.scrollview", jQuery.mobile.widget, {
options: {
- fps: 60, // Frames per second in msecs.
direction: null, // "x", "y", or null for both.
- scrollDuration: 2000, // Duration of the scrolling animation in msecs.
+ timerInterval: 10,
+ scrollDuration: 1000, // 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.
showScrollBars: true,
overshootEnable: false,
+ outerScrollEnable: true,
+ overflowEnable: true,
scrollJump: false,
},
+ _getViewHeight: function () {
+ return this._$view.height() + this._view_offset;
+ },
+
_makePositioned: function ( $ele ) {
if ( $ele.css("position") === "static" ) {
$ele.css( "position", "relative" );
},
_create: function () {
- var $page = $('.ui-page'),
- direction,
+ var direction,
self = this;
this._$clip = $( this.element ).addClass("ui-scrollview-clip");
- this._$view = this._$clip.wrapInner("<div></div>").children()
+
+ if ( this._$clip.children(".ui-scrollview-view").length ) {
+ this._$view = this._$clip.children(".ui-scrollview-view");
+ } else {
+ this._$view = this._$clip.wrapInner("<div></div>").children()
.addClass("ui-scrollview-view");
+ }
if ( this.options.scrollMethod === "translate" ) {
if ( this._$view.css("transform") === undefined ) {
this._makePositioned( this._$view );
this._$view.css( { left: 0, top: 0 } );
- this._view_height = this._$view.height();
+
+ this._view_offset = this._$view.offset().top - this._$clip.offset().top;
+ this._view_height = this._getViewHeight();
this._sx = 0;
this._sy = 0;
this._vTracker = ( direction !== "x" ) ?
new MomentumTracker( this.options ) : null;
- this._timerInterval = 1000 / this.options.fps;
+ this._timerInterval = this.options.timerInterval;
this._timerID = 0;
this._timerCB = function () {
this._add_event();
this._add_scrollbar();
this._add_scroll_jump();
+ this._add_overflow_indicator();
},
_startMScroll: function ( speedX, speedY ) {
this._stopMScroll();
this._showScrollBars();
+ this._showOverflowIndicator();
this._$clip.trigger( this.options.startEventName );
c = this._$clip.width();
v = this._$view.width();
+ if ( ( this._sx === 0 && speedX > 0 ) ||
+ ( this._sx === -(v - c) && speedX < 0 ) ) {
+ return;
+ }
+
ht.start( this._sx, speedX,
duration, (v > c) ? -(v - c) : 0, 0 );
keepGoing = !ht.done();
if ( vt ) {
c = this._$clip.height();
- v = this._$view.height() +
- parseFloat( this._$view.css("padding-top") );
+ v = this._getViewHeight();
+
+ if ( ( this._sy === 0 && speedY > 0 ) ||
+ ( this._sy === -(v - c) && speedY < 0 ) ) {
+ return;
+ }
vt.start( this._sy, speedY,
duration, (v > c) ? -(v - c) : 0, 0 );
}
this._hideScrollBars();
+ this._hideOverflowIndicator();
},
_handleMomentumScroll: function () {
var keepGoing = false,
x = 0,
y = 0,
+ scroll_height = 0,
+ self = this,
+ bouncing = function ( dir ) {
+ setTimeout( function () {
+ self._bouncing_dir = dir;
+ self._setBouncing( self._$view, "in" );
+ }, 100 );
+
+ setTimeout( function () {
+ self._setBouncing( self._$view, "out" );
+ }, 350 );
+ },
vt = this._vTracker,
ht = this._hTracker;
vt.update( this.options.overshootEnable );
y = vt.getPosition();
keepGoing = !vt.done();
+
+ if ( vt.getRemained() > this.options.overshootDuration ) {
+ scroll_height = this._getViewHeight() - this._$clip.height();
+
+ if ( vt.isMin() ) {
+ this._outerScroll( y - vt.getRemained() / 3, scroll_height );
+
+ if ( scroll_height > 0 ) {
+ bouncing( 1 );
+ }
+ } else if ( vt.isMax() ) {
+ this._outerScroll( vt.getRemained() / 3, scroll_height );
+
+ if ( scroll_height > 0 ) {
+ bouncing( 0 );
+ }
+ }
+ }
}
if ( ht ) {
var translate,
transition;
+ if ( this._bouncing ) {
+ return;
+ }
+
if ( !duration || duration === undefined ) {
transition = "none";
} else {
- transition = "-webkit-transform " + duration / 1000 + "s";
+ transition = "-webkit-transform " + duration / 1000 + "s ease-out";
}
if ( $.support.cssTransform3d ) {
});
},
+ _setBouncing: function ( $ele, dir ) {
+ if ( dir === "in" ) {
+ if ( this._bouncing ) {
+ return;
+ }
+
+ this._bouncing = true;
+ this._bouncing_count = 1;
+
+ this._bouncing_org_x = 1;
+ this._bouncing_org_y = 1;
+
+ this._bouncing_x = 0.99;
+ this._bouncing_y = 0.99;
+
+ this._setOverflowIndicator( this._bouncing_dir );
+ } else if ( dir === "out" ) {
+ if ( !this._bouncing ) {
+ return;
+ }
+
+ this._bouncing = false;
+ this._bouncing_count = 1;
+
+ this._bouncing_org_x = this._bouncing_x;
+ this._bouncing_org_y = this._bouncing_y;
+
+ this._bouncing_x = 1;
+ this._bouncing_y = 1;
+ this._setOverflowIndicator( this._bouncing_dir );
+ } else {
+ return;
+ }
+
+ this._doBouncing( $ele, dir );
+ },
+
+ _doBouncing: function ( $ele, dir ) {
+ var translate,
+ origin,
+ x_rate,
+ y_rate,
+ frame = 10,
+ self = this;
+
+ if ( $.support.cssTransform3d ) {
+ translate = "translate3d(" + this._sx + "px," + this._sy + "px, 0px)";
+ } else {
+ translate = "translate(" + this._sx + "px," + this._sy + "px)";
+ }
+
+ if ( dir === "in" ) {
+ x_rate = this._bouncing_org_x - ( this._bouncing_org_x -
+ this._bouncing_x ) / frame * this._bouncing_count;
+ y_rate = this._bouncing_org_y - ( this._bouncing_org_y -
+ this._bouncing_y ) / frame * this._bouncing_count;
+
+ translate += " scale(" + x_rate + "," + y_rate + ")";
+ } else if ( dir === "out" ) {
+ x_rate = this._bouncing_org_x + ( this._bouncing_x -
+ this._bouncing_org_x ) / frame * this._bouncing_count;
+ y_rate = this._bouncing_org_y + ( this._bouncing_y -
+ this._bouncing_org_y ) / frame * this._bouncing_count;
+
+ translate += " scale(" + x_rate + "," + y_rate + ")";
+ } else {
+ return;
+ }
+
+ if ( this._bouncing_dir ) {
+ origin = "50% " + ( this._bouncing_y * 100 - 10 ) + "%";
+ } else {
+ origin = "50% 10%";
+ }
+
+ $ele.css({
+ "-moz-transform": translate,
+ "-webkit-transform": translate,
+ "-ms-transform": translate,
+ "-o-transform": translate,
+ "transform": translate,
+ "-webkit-transform-origin": origin,
+ });
+
+ this._bouncing_count++;
+
+ if ( this._bouncing_count > frame ) {
+ return;
+ }
+
+ setTimeout( function () {
+ self._doBouncing( $ele, dir );
+ }, this._timerInterval );
+ },
+
_setCalibration: function ( x, y ) {
if ( this.options.overshootEnable ) {
this._sx = x;
}
if ( dirLock !== "x" && this._vTracker ) {
- scroll_height = $v.height() - $c.height() +
- parseFloat( $c.css("padding-top") ) +
- parseFloat( $c.css("padding-bottom") );
+ scroll_height = this._getViewHeight() - $c.height();
- this._outerScroll( y, scroll_height );
-
- if ( y >= 0 ) {
+ if ( y > 0 ) {
this._sy = 0;
+
+ if ( scroll_height > 0 ) {
+ this._bouncing_dir = 0;
+ this._setBouncing( this._$view, "in" );
+ }
} else if ( y < -scroll_height ) {
this._sy = -scroll_height;
+
+ if ( scroll_height > 0 ) {
+ this._bouncing_dir = 1;
+ this._setBouncing( this._$view, "in" );
+ }
} else {
this._sy = y;
}
$hsb = this._$hScrollBar,
$sbt;
- this._setCalibration( x, y );
-
- if ( this._outerScrolling ) {
+ if ( this._sx === x && this._sy === y ) {
return;
}
+ this._setCalibration( x, y );
+
x = this._sx;
y = this._sy;
if ( sm === "translate" ) {
this._setElementTransform( $sbt, "0px",
- -y / $v.height() * $sbt.parent().height() + "px",
+ -y / this._getViewHeight() * $sbt.parent().height() + "px",
duration );
} else {
- $sbt.css( "top", -y / $v.height() * 100 + "%" );
+ $sbt.css( "top", -y / this._getViewHeight() * 100 + "%" );
}
}
_outerScroll: function ( y, scroll_height ) {
var self = this,
- top = $( window ).scrollTop(),
+ top = $( window ).scrollTop() - window.screenTop,
sy = 0,
duration = this.options.snapbackDuration,
start = getCurrentTime(),
tfunc;
- if ( this._$clip.jqmData("scroll") !== "y" ) {
+ if ( !this.options.outerScrollEnable ) {
return;
}
- if ( this._outerScrolling ) {
+ if ( this._$clip.jqmData("scroll") !== "y" ) {
return;
}
- if ( !this._dragging ) {
+ if ( this._outerScrolling ) {
return;
}
}
if ( y > 0 ) {
- sy = -y;
+ sy = ( window.screenTop ? window.screenTop : -y );
} else if ( y < -scroll_height ) {
sy = -y - scroll_height;
} else {
return;
}
- sy *= 10;
-
tfunc = function () {
var elapsed = getCurrentTime() - start;
if ( elapsed >= duration ) {
window.scrollTo( 0, top + sy );
self._outerScrolling = undefined;
+
+ self._stopMScroll();
} else {
- ec = $.easing.easeOutQuad( elapsed / duration, elapsed, 0, 1, duration );
+ ec = $.easing.easeOutQuad( elapsed / duration,
+ elapsed, 0, 1, duration );
window.scrollTo( 0, top + ( sy * ec ) );
self._outerScrolling = setTimeout( tfunc, self._timerInterval );
}
};
this._outerScrolling = setTimeout( tfunc, self._timerInterval );
-
- /* skip the srollview dragging */
- this._skip_dragging = true;
},
_scrollTo: function ( x, y, duration ) {
target.is( '.ui-btn-inner' ) ||
target.is( '.ui-btn-inner .ui-icon' );
+ if ( this._is_button ) {
+ if ( target.parents('.ui-slider-handle').length ) {
+ this._skip_dragging = true;
+ return;
+ }
+ }
+
/*
* We need to prevent the default behavior to
* suppress accidental selection of text, etc.
});
}
+ if ( this.options.eventType === "mouse" && !this._is_inputbox && !this._is_button ) {
+ e.preventDefault();
+ }
+
this._lastX = ex;
this._lastY = ey;
this._startY = ey;
_propagateDragMove: function ( sv, e, ex, ey, dir ) {
this._hideScrollBars();
+ this._hideOverflowIndicator();
this._disableTracking();
sv._handleDragStart( e, ex, ey );
sv._directionLock = dir;
newY,
dirLock;
- if ( Math.abs( this._startY - ey ) < mt && !this._didDrag ) {
- return;
- }
-
this._lastMove = getCurrentTime();
if ( !this._directionLock ) {
}
if ( dirLock !== "x" && this._vTracker ) {
+ if ( Math.abs( this._startY - ey ) < mt && dirLock !== "xy" ) {
+ return;
+ }
+
y = this._sy;
this._speedY = dy;
newY = y + dy;
this._setScrollPosition( newX, newY );
this._showScrollBars();
+ this._showOverflowIndicator();
},
_handleDragStop: function ( e ) {
y;
if ( sx || sy ) {
- this._startMScroll( sx, sy );
+ if ( !this._setGestureScroll( sx, sy ) ) {
+ this._startMScroll( sx, sy );
+ }
} else {
this._hideScrollBars();
+ this._hideOverflowIndicator();
}
this._disableTracking();
+ if ( this._bouncing ) {
+ this._setBouncing( this._$view, "out" );
+ this._hideScrollBars();
+ this._hideOverflowIndicator();
+ }
+
return !this._didDrag;
},
+ _setGestureScroll: function ( sx, sy ) {
+ var self = this,
+ reset = function () {
+ clearTimeout( self._gesture_timer );
+ self._gesture_dir = 0;
+ self._gesture_count = 0;
+ self._gesture_timer = undefined;
+ };
+
+ if ( !sy ) {
+ return false;
+ }
+
+ dir = sy > 0 ? 1 : -1;
+
+ if ( !this._gesture_timer ) {
+ this._gesture_count = 1;
+ this._gesture_dir = dir;
+
+ this._gesture_timer = setTimeout( function () {
+ reset();
+ }, 1000 );
+
+ return false;
+ }
+
+ if ( this._gesture_dir !== dir ) {
+ reset();
+ return false;
+ }
+
+ this._gesture_count++;
+
+ if ( this._gesture_count === 3 ) {
+ if ( dir > 0 ) {
+ this.scrollTo( 0, 0, this.options.overshootDuration );
+ } else {
+ this.scrollTo( 0, -( this._getViewHeight() - this._$clip.height() ),
+ this.options.overshootDuration );
+ }
+ reset();
+
+ return true;
+ }
+
+ return false;
+ },
+
_enableTracking: function () {
this._dragging = true;
},
this._dragging = false;
},
- _showScrollBars: function () {
- var vclass = "ui-scrollbar-visible";
+ _showScrollBars: function ( interval ) {
+ var vclass = "ui-scrollbar-visible",
+ self = this;
if ( !this.options.showScrollBars ) {
return;
}
this._scrollbar_showed = true;
+
+ if ( interval ) {
+ setTimeout( function () {
+ self._hideScrollBars();
+ }, interval );
+ }
},
_hideScrollBars: function () {
this._scrollbar_showed = false;
},
+ _resetOverflowIndicator: function () {
+ if ( !this.options.overflowEnable || !this._overflowAvail ) {
+ return;
+ }
+
+ this._overflow_top.css( "-webkit-animation", "" );
+ this._overflow_bottom.css( "-webkit-animation", "" );
+ },
+
+ _setOverflowIndicator: function ( dir ) {
+ if ( dir === 1 ) {
+ this._opacity_top = "0.2";
+ this._opacity_bottom = "0.8";
+ } else if ( dir === 0 ) {
+ this._opacity_top = "0.8";
+ this._opacity_bottom = "0.2";
+ } else {
+ this._opacity_top = "0.5";
+ this._opacity_bottom = "0.5";
+ }
+ },
+
+ _getOverflowIndicator: function ( opacity ) {
+ if ( opacity === "0.2" ) {
+ return "-lite";
+ } else if ( opacity === "0.8" ) {
+ return "-dark";
+ }
+ return "";
+ },
+
+ _showOverflowIndicator: function () {
+ if ( !this.options.overflowEnable || !this._overflowAvail ) {
+ return;
+ }
+
+ this._overflow_top.css( "opacity", this._opacity_top );
+ this._overflow_bottom.css( "opacity", this._opacity_bottom );
+
+ if ( this._overflow_showed === true ) {
+ return;
+ }
+
+ this._overflow_top.css( "-webkit-animation", "ui-overflow-show" +
+ this._getOverflowIndicator( this._opacity_top ) + " 0.3s 1 ease" );
+ this._overflow_bottom.css( "-webkit-animation", "ui-overflow-show" +
+ this._getOverflowIndicator( this._opacity_bottom ) + " 0.3s 1 ease" );
+
+ this._overflow_showed = true;
+ },
+
+ _hideOverflowIndicator: function () {
+ var opacity_top,
+ opacity_bottom;
+
+ if ( !this.options.overflowEnable || !this._overflowAvail ) {
+ return;
+ }
+
+ if ( this._overflow_showed === false ) {
+ return;
+ }
+
+ opacity_top = this._overflow_top.css( "opacity" );
+ opacity_bottom = this._overflow_bottom.css( "opacity" );
+
+ this._overflow_top.css( "opacity", "0" );
+ this._overflow_bottom.css( "opacity", "0" );
+
+ this._overflow_top.css( "-webkit-animation", "ui-overflow-hide" +
+ this._getOverflowIndicator( opacity_top ) + " 0.5s 1 ease" );
+ this._overflow_bottom.css( "-webkit-animation", "ui-overflow-hide" +
+ this._getOverflowIndicator( opacity_bottom ) + " 0.5s 1 ease" );
+
+ this._overflow_showed = false;
+ this._setOverflowIndicator();
+ },
+
_add_event: function () {
var self = this,
$c = this._$clip,
$v.bind( this._dragEvt, this._dragCB );
- if ( $c.jqmData("scroll") !== "y" ) {
- return;
- }
-
$c.bind( "updatelayout", function ( e ) {
- var $page = $c.parentsUntil("ui-page"),
- sy,
- vh;
+ var sy,
+ vh,
+ view_h = self._getViewHeight();
- if ( !$c.height() || !$v.height() ) {
+ if ( !$c.height() || !view_h ) {
self.scrollTo( 0, 0, 0 );
return;
}
- sy = $c.height() - $v.height();
- vh = $v.height() - self._view_height;
+ sy = $c.height() - view_h;
+ vh = view_h - self._view_height;
- self._view_height = $v.height();
+ self._view_height = view_h;
if ( vh == 0 || vh > $c.height() / 2 ) {
return;
}
if ( self._sy - sy <= -vh ) {
- self.scrollTo( 0, self._sy,
+ self.scrollTo( 0, sy,
self.options.snapbackDuration );
} else if ( self._sy - sy <= vh + self.options.moveThreshold ) {
self.scrollTo( 0, sy,
});
$( window ).bind( "resize", function ( e ) {
- var $page = $c.parentsUntil("ui-page"),
- focused;
+ var focused,
+ view_h = self._getViewHeight();
+
+ if ( $(".ui-page-active").get(0) !== $c.closest(".ui-page").get(0) ) {
+ return;
+ }
- if ( !$c.height() || !$v.height() ) {
+ if ( !$c.height() || !view_h ) {
return;
}
focused.trigger("resize.scrollview");
}
- /* calibration */
- if ( self._sy < $c.height() - $v.height() ) {
- self.scrollTo( 0, self._sy,
- self.options.snapbackDuration );
- }
+ /* calibration - after triggered throttledresize */
+ setTimeout( function () {
+ if ( self._sy < $c.height() - self._getViewHeight() ) {
+ self.scrollTo( 0, self._sy,
+ self.options.snapbackDuration );
+ }
+ }, 260 );
- self._view_height = $v.height();
+ self._view_height = view_h;
});
+
+ $c.closest(".ui-page")
+ .one( "pageshow", function ( e ) {
+ self._view_offset = self._$view.offset().top - self._$clip.offset().top;
+ self._view_height = self._getViewHeight();
+ })
+ .bind( "pageshow", function ( e ) {
+ /* should be called after pagelayout */
+ setTimeout( function () {
+ self._set_scrollbar_size();
+ self._setScrollPosition( self._sx, self._sy );
+ self._showScrollBars( 2000 );
+ self._resetOverflowIndicator();
+ }, 0 );
+ });
},
_add_scrollbar: function () {
}
if ( this._vTracker ) {
- top_btn = $( '<div class="ui-scroll-jump-top-bg ui-btn" data-theme="s">' +
- '<div class="ui-scroll-jump-top"></div></div>' );
- $c.append( top_btn );
+ top_btn = $( '<div class="ui-scroll-jump-top-bg">' +
+ '<div data-role="button" data-inline="true" data-icon="jumptop" style="width:37px;height:37px">.</div></div>' );
+ $c.append( top_btn ).trigger("create");
top_btn.bind( "vclick", function () {
self.scrollTo( 0, 0, self.options.overshootDuration );
}
if ( this._hTracker ) {
- left_btn = $( '<div class="ui-scroll-jump-left-bg ui-btn" data-theme="s">' +
- '<div class="ui-scroll-jump-left"></div></div>' );
- $c.append( left_btn );
+ left_btn = $( '<div class="ui-scroll-jump-left-bg">' +
+ '<div data-role="button" data-inline="true" data-icon="jumpleft" style="width:37px;height:37px">.</div></div>' );
+ $c.append( left_btn ).trigger("create");
left_btn.bind( "vclick", function () {
self.scrollTo( 0, 0, self.options.overshootDuration );
}
},
+ _add_overflow_indicator: function () {
+ if ( !this.options.overflowEnable ) {
+ return;
+ }
+
+ this._overflow_top = $( '<div class="ui-overflow-indicator-top"></div>' );
+ this._overflow_bottom = $( '<div class="ui-overflow-indicator-bottom"></div>' );
+
+ this._$clip.append( this._overflow_top );
+ this._$clip.append( this._overflow_bottom );
+
+ this._opacity_top = "0.5";
+ this._opacity_bottom = "0.5";
+ this._overflow_showed = false;
+ },
+
_set_scrollbar_size: function () {
var $c = this._$clip,
$v = this._$view,
}
if ( this._$hScrollBar && vw ) {
thumb = this._$hScrollBar.find(".ui-scrollbar-thumb");
- thumb.css( "width", (cw >= vw ? "100%" :
+ thumb.css( "width", (cw >= vw ? "0" :
(Math.floor(cw / vw * 100) || 1) + "%") );
}
}
if ( this._vTracker ) {
ch = $c.height();
- vh = $v.height();
+ vh = this._getViewHeight();
this._maxY = ch - vh;
if ( this._maxY > 0 ) {
}
if ( this._$vScrollBar && vh ) {
thumb = this._$vScrollBar.find(".ui-scrollbar-thumb");
- thumb.css( "height", (ch >= vh ? "100%" :
+ thumb.css( "height", (ch >= vh ? "0" :
(Math.floor(ch / vh * 100) || 1) + "%") );
+
+ this._overflowAvail = !!thumb.height();
}
}
}
this.minPos = 0;
this.maxPos = 0;
this.duration = 0;
+ this.remained = 0;
},
update: function ( overshootEnable ) {
elapsed = elapsed > duration ? duration : elapsed;
+ this.remained = duration - elapsed;
+
if ( state === tstates.scrolling || state === tstates.overshot ) {
dx = this.speed *
( 1 - $.easing[this.easing]( elapsed / duration,
this.pos = x;
if ( state === tstates.overshot ) {
+ if ( !overshootEnable ) {
+ this.state = tstates.done;
+ }
if ( elapsed >= duration ) {
this.state = tstates.snapback;
this.fromPos = this.pos;
return this.state === tstates.done;
},
+ isMin: function () {
+ return this.pos === this.minPos;
+ },
+
+ isMax: function () {
+ return this.pos === this.maxPos;
+ },
+
+ getRemained: function () {
+ return this.remained;
+ },
+
getPosition: function () {
return this.pos;
}
$( this ).scrolllistview();
} else {
var st = $( this ).jqmData("scroll"),
- dir = st && ( st.search(/^[xy]/) !== -1 ) ? st.charAt(0) : null,
+ dir = st && ( st.search(/^[xy]/) !== -1 ) ? st : null,
+ content = $(this).hasClass("ui-content"),
opts;
if ( st === "none" ) {
opts = {
direction: dir || undefined,
+ overflowEnable: content,
scrollMethod: $( this ).jqmData("scroll-method") || undefined,
scrollJump: $( this ).jqmData("scroll-jump") || undefined
};