diff --git a/dist/js/ionic.js b/dist/js/ionic.js index ddddfb481f..d221228ab3 100644 --- a/dist/js/ionic.js +++ b/dist/js/ionic.js @@ -2490,11 +2490,15 @@ window.ionic = { // Extend the options with our defaults ionic.Utils.extend(opts, { - decelerationRate: ionic.views.Scroll.prototype.DECEL_RATE_NORMAL + decelerationRate: ionic.views.Scroll.prototype.DECEL_RATE_NORMAL, + dragThresholdY: 10, + resistance: 2 }); this.el = opts.el; - this.decelerationRate = opts.decelerationRate + this.decelerationRate = opts.decelerationRate; + this.dragThresholdY = opts.dragThresholdY; + this.resistance = opts.resistance; // Listen for drag and release events window.ionic.onGesture('drag', function(e) { @@ -2509,9 +2513,33 @@ window.ionic = { DECEL_RATE_NORMAL: 0.998, DECEL_RATE_FAST: 0.99, + /** + * Scroll to the given X and Y point, taking + * the given amount of time, with the given + * easing function defined as a CSS3 timing function. + * + * @param {float} the x position to scroll to (CURRENTLY NOT SUPPORTED!) + * @param {float} the y position to scroll to + * @param {float} the time to take scrolling to the new position + * @param {easing} the animation function to use for easing + */ + scrollTo: function(x, y, time, easing) { + easing = easing || 'cubic-bezier(0.1, 0.57, 0.1, 1)'; + + var el = this.el; + + el.style.webkitTransitionTimingFunction = easing; + el.style.webkitTransitionDuration = time; + el.style.webkitTransform = 'translate3d(0,' + y + 'px, 0)'; + }, + + _initDrag: function() { this._isDragging = false; this._drag = null; + this.el.classList.remove('scroll-scrolling'); + this.el.style.webkitTransitionDuration = '0'; + }, /** @@ -2523,12 +2551,12 @@ window.ionic = { this._initDrag(); - this.el.classList.remove('scroll-scrolling'); - - var scrollTop = parseFloat(this.el.scrollTop); + var scrollTop = parseFloat(this.el.style.webkitTransform.replace('translate3d(', '').split(',')[1]) || 0; this._drag = { - startY: scrollTop + startY: scrollTop, + resist: 1, + startTime: +(new Date) }; }, @@ -2545,24 +2573,99 @@ window.ionic = { if(!_this._drag) { _this._startDrag(e); } - console.log('At scroll top', _this.el.scrollTop, e.gesture.deltaY); - _this.el.style.webkitTransform = 'translate3d(0,' + e.gesture.deltaY + 'px, 0)'; + // Stop any default events during the drag + e.preventDefault(); + + // Check if we should start dragging. Check if we've dragged past the threshold. + if(!_this._isDragging && (Math.abs(e.gesture.deltaY) > _this.dragThresholdY)) { + _this._isDragging = true; + } + + if(_this._isDragging) { + var totalHeight = _this.el.offsetHeight; + var parentHeight = _this.el.parentNode.offsetHeight; + + var newY = _this._drag.startY + e.gesture.deltaY; + + // Check if the dragging is beyond the bottom or top + if(newY > 0 || (-newY + parentHeight) > totalHeight) { + // Rubber band + newY = newY + e.gesture.deltaY / (-_this.resistance); + } + // Update the new translated Y point of the container + _this.el.style.webkitTransform = 'translate3d(0,' + newY + 'px, 0)'; + } }); }, + _handleEndDrag: function(e) { var _this = this; - window.requestAnimationFrame(function() { - - // We didn't have a drag, so just init and leave - if(!_this._drag) { - _this._initDrag(); - return; + // We didn't have a drag, so just init and leave + if(!_this._drag) { + _this._initDrag(); + return; + } + + // Animate to the finishing point + _this._animateToStop(e); + // Cleanup + _this._initDrag(); + + }, + + + _animateToStop: function(e) { + var _this = this; + + var totalHeight = this.el.offsetHeight; + var parentHeight = this.el.parentNode.offsetHeight; + var scrollTop = parseFloat(this.el.style.webkitTransform.replace('translate3d(', '').split(',')[1]) || 0; + var duration = +(new Date) - this._drag.startTime; + + var newY = scrollTop; + + if(e.gesture.velocityY) { + // Get the final resting point + var vy = e.gesture.velocityY; + var speed = Math.abs(e.gesture.deltaY) / duration; + //newY = newY + (vy * vy) / (0.05 * this.decelerationRate); + + var destination = newY + ( speed * speed ) / ( 2 * (1-_this.decelerationRate)) * ( e.gesture.deltaY < 0 ? -1 : 1 ); + var dur = speed / (1-_this.decelerationRate); + + if((-destination + parentHeight) > totalHeight) { + destination = -(totalHeight - parentHeight); + } else if(destination > 0) { + destination = 0; } - _this._initDrag(); - }); + console.log('Ending at velocity and point', speed, vy, destination, dur); + + + var el = this.el; + + window.requestAnimationFrame(function() { + _this.scrollTo(0, destination, dur); + }); + } + + /* + // Check if the dragging is beyond the bottom or top + */ + + /* + setTimeout(function() { + window.requestAnimationFrame(function() { + _this.el.style.webkitTransform = 'translate3d(0,' + newY + 'px, 0)'; + }); + }, 50); + */ + + // Turn on animation + this.el.classList.add('scroll-scrolling'); + } }; diff --git a/js/views/scrollView.js b/js/views/scrollView.js index c9f884bd5f..73a3532758 100644 --- a/js/views/scrollView.js +++ b/js/views/scrollView.js @@ -6,11 +6,15 @@ // Extend the options with our defaults ionic.Utils.extend(opts, { - decelerationRate: ionic.views.Scroll.prototype.DECEL_RATE_NORMAL + decelerationRate: ionic.views.Scroll.prototype.DECEL_RATE_NORMAL, + dragThresholdY: 10, + resistance: 2 }); this.el = opts.el; - this.decelerationRate = opts.decelerationRate + this.decelerationRate = opts.decelerationRate; + this.dragThresholdY = opts.dragThresholdY; + this.resistance = opts.resistance; // Listen for drag and release events window.ionic.onGesture('drag', function(e) { @@ -25,9 +29,33 @@ DECEL_RATE_NORMAL: 0.998, DECEL_RATE_FAST: 0.99, + /** + * Scroll to the given X and Y point, taking + * the given amount of time, with the given + * easing function defined as a CSS3 timing function. + * + * @param {float} the x position to scroll to (CURRENTLY NOT SUPPORTED!) + * @param {float} the y position to scroll to + * @param {float} the time to take scrolling to the new position + * @param {easing} the animation function to use for easing + */ + scrollTo: function(x, y, time, easing) { + easing = easing || 'cubic-bezier(0.1, 0.57, 0.1, 1)'; + + var el = this.el; + + el.style.webkitTransitionTimingFunction = easing; + el.style.webkitTransitionDuration = time; + el.style.webkitTransform = 'translate3d(0,' + y + 'px, 0)'; + }, + + _initDrag: function() { this._isDragging = false; this._drag = null; + this.el.classList.remove('scroll-scrolling'); + this.el.style.webkitTransitionDuration = '0'; + }, /** @@ -39,12 +67,12 @@ this._initDrag(); - this.el.classList.remove('scroll-scrolling'); - - var scrollTop = parseFloat(this.el.scrollTop); + var scrollTop = parseFloat(this.el.style.webkitTransform.replace('translate3d(', '').split(',')[1]) || 0; this._drag = { - startY: scrollTop + startY: scrollTop, + resist: 1, + startTime: +(new Date) }; }, @@ -61,24 +89,99 @@ if(!_this._drag) { _this._startDrag(e); } - console.log('At scroll top', _this.el.scrollTop, e.gesture.deltaY); - _this.el.style.webkitTransform = 'translate3d(0,' + e.gesture.deltaY + 'px, 0)'; + // Stop any default events during the drag + e.preventDefault(); + + // Check if we should start dragging. Check if we've dragged past the threshold. + if(!_this._isDragging && (Math.abs(e.gesture.deltaY) > _this.dragThresholdY)) { + _this._isDragging = true; + } + + if(_this._isDragging) { + var totalHeight = _this.el.offsetHeight; + var parentHeight = _this.el.parentNode.offsetHeight; + + var newY = _this._drag.startY + e.gesture.deltaY; + + // Check if the dragging is beyond the bottom or top + if(newY > 0 || (-newY + parentHeight) > totalHeight) { + // Rubber band + newY = newY + e.gesture.deltaY / (-_this.resistance); + } + // Update the new translated Y point of the container + _this.el.style.webkitTransform = 'translate3d(0,' + newY + 'px, 0)'; + } }); }, + _handleEndDrag: function(e) { var _this = this; - window.requestAnimationFrame(function() { - - // We didn't have a drag, so just init and leave - if(!_this._drag) { - _this._initDrag(); - return; + // We didn't have a drag, so just init and leave + if(!_this._drag) { + _this._initDrag(); + return; + } + + // Animate to the finishing point + _this._animateToStop(e); + // Cleanup + _this._initDrag(); + + }, + + + _animateToStop: function(e) { + var _this = this; + + var totalHeight = this.el.offsetHeight; + var parentHeight = this.el.parentNode.offsetHeight; + var scrollTop = parseFloat(this.el.style.webkitTransform.replace('translate3d(', '').split(',')[1]) || 0; + var duration = +(new Date) - this._drag.startTime; + + var newY = scrollTop; + + if(e.gesture.velocityY) { + // Get the final resting point + var vy = e.gesture.velocityY; + var speed = Math.abs(e.gesture.deltaY) / duration; + //newY = newY + (vy * vy) / (0.05 * this.decelerationRate); + + var destination = newY + ( speed * speed ) / ( 2 * (1-_this.decelerationRate)) * ( e.gesture.deltaY < 0 ? -1 : 1 ); + var dur = speed / (1-_this.decelerationRate); + + if((-destination + parentHeight) > totalHeight) { + destination = -(totalHeight - parentHeight); + } else if(destination > 0) { + destination = 0; } - _this._initDrag(); - }); + console.log('Ending at velocity and point', speed, vy, destination, dur); + + + var el = this.el; + + window.requestAnimationFrame(function() { + _this.scrollTo(0, destination, dur); + }); + } + + /* + // Check if the dragging is beyond the bottom or top + */ + + /* + setTimeout(function() { + window.requestAnimationFrame(function() { + _this.el.style.webkitTransform = 'translate3d(0,' + newY + 'px, 0)'; + }); + }, 50); + */ + + // Turn on animation + this.el.classList.add('scroll-scrolling'); + } }; diff --git a/test/scroll.html b/test/scroll.html index 8ad4eeb015..c78f77a1a5 100644 --- a/test/scroll.html +++ b/test/scroll.html @@ -6,23 +6,37 @@ + - -
+
Edit

Scroll Me

Delete
-
-
+
+ +
    +