From ab38d805d2c0c5308fa005dbf7831ed3377f27c0 Mon Sep 17 00:00:00 2001 From: Max Lynch Date: Fri, 25 Oct 2013 13:37:20 -0500 Subject: [PATCH] Scroll fixes --- dist/css/ionic-ios7.css | 24 +++---- dist/css/ionic-scoped.css | 24 +++---- dist/css/ionic.css | 43 ++++++++---- dist/js/ionic.js | 61 +++++++++++++---- js/views/scrollView.js | 38 +++++++---- js/views/scrollerView.js | 23 ++++++- scss/ionic/_scroll.scss | 19 +++++- test/scroll_forms.html | 135 ++++++++++++++++++++++++++++++++++++-- test/scroll_horiz.html | 2 +- 9 files changed, 298 insertions(+), 71 deletions(-) diff --git a/dist/css/ionic-ios7.css b/dist/css/ionic-ios7.css index 2b49a29820..e93a18a792 100644 --- a/dist/css/ionic-ios7.css +++ b/dist/css/ionic-ios7.css @@ -182,7 +182,7 @@ sub { fieldset { margin: 0 2px; padding: 0.35em 0.625em 0.75em; - border: 1px solid #c0c0c0; } + border: 1px solid silver; } /** * 1. Correct `color` not being inherited in IE 8/9. @@ -558,7 +558,7 @@ a.subdued‎ { &.bar-footer { border-top-width: 1px; }*/ - box-shadow: inset 0 0 1px #999; } + box-shadow: inset 0 0 1px #999999; } .bar.bar-clear { border: none; background: none; @@ -1621,7 +1621,7 @@ input[type="file"] { line-height: 34px; } select { - border: 1px solid #ccc; + border: 1px solid #cccccc; background-color: white; } select[multiple], @@ -1693,7 +1693,7 @@ input[type="checkbox"][readonly] { right: 20px; transition: 0.2s ease; transition-property: left, right; - transition-delay: 0s, .05s; } + transition-delay: 0s, 0.05s; } .toggle :checked + .track { /* When the toggle is "on" */ @@ -1708,7 +1708,7 @@ input[type="checkbox"][readonly] { right: 0; left: 20px; -webkit-transform: none; - transition-delay: .05s, 0s; } + transition-delay: 0.05s, 0s; } .item-radio .item-content { /* give some room to the right for the checkmark icon */ @@ -1978,7 +1978,7 @@ input[type="range"] { .button.button-icon:active, .button.button-icon.active { background: none; box-shadow: none; - text-shadow: 0px 0px 10px #fff; } + text-shadow: 0px 0px 10px white; } .button.block, .button.button-full { margin-top: 10px; margin-bottom: 10px; } @@ -2317,9 +2317,9 @@ a.button { line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-animation: spin .75s linear infinite; - -moz-animation: spin .75s linear infinite; - animation: spin .75s linear infinite; } + -webkit-animation: spin 0.75s linear infinite; + -moz-animation: spin 0.75s linear infinite; + animation: spin 0.75s linear infinite; } .icon-loading:before { content: "\e144"; } @@ -2332,9 +2332,9 @@ a.button { line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-animation: spin .75s linear infinite; - -moz-animation: spin .75s linear infinite; - animation: spin .75s linear infinite; } + -webkit-animation: spin 0.75s linear infinite; + -moz-animation: spin 0.75s linear infinite; + animation: spin 0.75s linear infinite; } .icon-refreshing:before { content: "\e144"; } diff --git a/dist/css/ionic-scoped.css b/dist/css/ionic-scoped.css index cd80eb3f07..379688e75b 100644 --- a/dist/css/ionic-scoped.css +++ b/dist/css/ionic-scoped.css @@ -1027,7 +1027,7 @@ .ionic fieldset { margin: 0 2px; padding: 0.35em 0.625em 0.75em; - border: 1px solid #c0c0c0; } + border: 1px solid silver; } .ionic legend { padding: 0; /* 2 */ @@ -1324,7 +1324,7 @@ &.bar-footer { border-top-width: 1px; }*/ - box-shadow: inset 0 0 1px #999; } + box-shadow: inset 0 0 1px #999999; } .ionic .bar.bar-clear { border: none; background: none; @@ -2277,7 +2277,7 @@ .ionic input[type="file"] { line-height: 34px; } .ionic select { - border: 1px solid #ccc; + border: 1px solid #cccccc; background-color: white; } .ionic select[multiple], .ionic select[size] { @@ -2326,7 +2326,7 @@ border-radius: 50%; background: white; content: ' '; - transition: background-color .1s ease-in-out; } + transition: background-color 0.1s ease-in-out; } .ionic .checkbox input:after { position: absolute; top: 34%; @@ -2339,7 +2339,7 @@ border-right: 0; content: ' '; opacity: 0; - transition: opacity .05s ease-in-out; + transition: opacity 0.05s ease-in-out; -webkit-transform: rotate(-45deg); transform: rotate(-45deg); } .ionic .checkbox input:checked:before { @@ -2677,7 +2677,7 @@ .ionic .button.button-icon:active, .ionic .button.button-icon.active { background: none; box-shadow: none; - text-shadow: 0px 0px 10px #fff; } + text-shadow: 0px 0px 10px white; } .ionic .button.block, .ionic .button.button-full { margin-top: 10px; margin-bottom: 10px; } @@ -2926,9 +2926,9 @@ line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-animation: spin .75s linear infinite; - -moz-animation: spin .75s linear infinite; - animation: spin .75s linear infinite; } + -webkit-animation: spin 0.75s linear infinite; + -moz-animation: spin 0.75s linear infinite; + animation: spin 0.75s linear infinite; } .ionic .icon-loading:before { content: "\e144"; } .ionic .icon-refreshing { @@ -2940,9 +2940,9 @@ line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-animation: spin .75s linear infinite; - -moz-animation: spin .75s linear infinite; - animation: spin .75s linear infinite; } + -webkit-animation: spin 0.75s linear infinite; + -moz-animation: spin 0.75s linear infinite; + animation: spin 0.75s linear infinite; } .ionic .icon-refreshing:before { content: "\e144"; } .ionic .hidden, diff --git a/dist/css/ionic.css b/dist/css/ionic.css index 86bbb57db1..8a814590e0 100644 --- a/dist/css/ionic.css +++ b/dist/css/ionic.css @@ -1261,7 +1261,7 @@ sub { fieldset { margin: 0 2px; padding: 0.35em 0.625em 0.75em; - border: 1px solid #c0c0c0; } + border: 1px solid silver; } /** * 1. Correct `color` not being inherited in IE 8/9. @@ -1667,7 +1667,7 @@ a.subdued‎ { &.bar-footer { border-top-width: 1px; }*/ - box-shadow: inset 0 0 1px #999; } + box-shadow: inset 0 0 1px #999999; } .bar.bar-clear { border: none; background: none; @@ -2764,7 +2764,7 @@ input[type="file"] { line-height: 34px; } select { - border: 1px solid #ccc; + border: 1px solid #cccccc; background-color: white; } select[multiple], @@ -2819,7 +2819,7 @@ input[type="checkbox"][readonly] { border-radius: 50%; background: white; content: ' '; - transition: background-color .1s ease-in-out; } + transition: background-color 0.1s ease-in-out; } /* the checkmark within the box */ .checkbox input:after { @@ -2834,7 +2834,7 @@ input[type="checkbox"][readonly] { border-right: 0; content: ' '; opacity: 0; - transition: opacity .05s ease-in-out; + transition: opacity 0.05s ease-in-out; -webkit-transform: rotate(-45deg); transform: rotate(-45deg); } @@ -3199,7 +3199,7 @@ input[type="range"] { .button.button-icon:active, .button.button-icon.active { background: none; box-shadow: none; - text-shadow: 0px 0px 10px #fff; } + text-shadow: 0px 0px 10px white; } .button.block, .button.button-full { margin-top: 10px; margin-bottom: 10px; } @@ -3417,7 +3417,24 @@ a.button { */ .scroll { position: absolute; - width: 100%; } + z-index: 1; + -webkit-tap-highlight-color: rgba(0, 0, 0, 0); + width: 100%; + -webkit-transform: translateZ(0); + -moz-transform: translateZ(0); + -ms-transform: translateZ(0); + -o-transform: translateZ(0); + transform: translateZ(0); + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-text-size-adjust: none; + -moz-text-size-adjust: none; + -ms-text-size-adjust: none; + -o-text-size-adjust: none; + text-size-adjust: none; } .nav-content { position: absolute; @@ -3589,9 +3606,9 @@ a.button { line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-animation: spin .75s linear infinite; - -moz-animation: spin .75s linear infinite; - animation: spin .75s linear infinite; } + -webkit-animation: spin 0.75s linear infinite; + -moz-animation: spin 0.75s linear infinite; + animation: spin 0.75s linear infinite; } .icon-loading:before { content: "\e144"; } @@ -3604,9 +3621,9 @@ a.button { line-height: 1; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; - -webkit-animation: spin .75s linear infinite; - -moz-animation: spin .75s linear infinite; - animation: spin .75s linear infinite; } + -webkit-animation: spin 0.75s linear infinite; + -moz-animation: spin 0.75s linear infinite; + animation: spin 0.75s linear infinite; } .icon-refreshing:before { content: "\e144"; } diff --git a/dist/js/ionic.js b/dist/js/ionic.js index ba804a07d1..3ae0e8279b 100644 --- a/dist/js/ionic.js +++ b/dist/js/ionic.js @@ -2614,7 +2614,7 @@ window.ionic = { } */ - console.log('Momentum of time', time, speed, destination, duration); + console.log('Momentum: time:', time, 'speed:',speed, 'dest:',destination, 'dur:',duration); return { destination: Math.round(destination), duration: duration @@ -2801,7 +2801,6 @@ window.ionic = { newY = 0; } - console.log(newX, newY); // Update the new translated Y point of the container _this.el.style.webkitTransform = 'translate3d(' + newX + 'px,' + newY + 'px, 0)'; @@ -2809,6 +2808,8 @@ window.ionic = { _this.x = newX; _this.y = newY; + console.log('Moving to', newX, newY); + // Check if we need to reset the drag initial states if we've // been dragging for a bit if(timestamp - drag.startTime > 300) { @@ -2862,30 +2863,41 @@ window.ionic = { var parentHeight = _this.el.offsetHeight; // Calculate how long we've been dragging for, with a max of 300ms - var duration = Math.min(300, (Date.now()) - _this._drag.startTime); + var duration = Date.now() - _this._drag.startTime; var newX = Math.round(_this.x); var newY = Math.round(_this.y); + + this.scrollTo(newX, newY); + + var distanceX = Math.abs(newX - _this.startX); + var distanceY = Math.abs(newY - _this.startY); + this.endTime = Date.now(); + //distanceX = Math.abs(newX - this.startX), //var distanceY = Math.abs(newY - drag.startY); + + // If the duration is within reasonable bounds, enable momentum scrolling + if(duration < 300) { + var momentumX = _this._getMomentum(_this.x, drag.startX, duration, parentWidth - totalWidth, parentWidth); + var momentumY = _this._getMomentum(_this.y, drag.startY, duration, parentHeight - totalHeight, parentHeight); + //var newX = momentumX.destination; + newX = momentumX.destination; + newY = momentumY.destination; - - var momentumX = _this._getMomentum(_this.x, drag.startX, duration, parentWidth - totalWidth, parentWidth); - var momentumY = _this._getMomentum(_this.y, drag.startY, duration, parentHeight - totalHeight, parentHeight); - //var newX = momentumX.destination; - newX = momentumX.destination; - newY = momentumY.destination; - - var timeX = momentumX.duration; - var timeY = momentumY.duration; + var timeX = momentumX.duration; + var timeY = momentumY.duration; + } // Check if we need to rubber band back if(_this.x > 0) { _this.scrollTo(0, 0, _this.bounceTime); + return; } else if((-_this.x + parentWidth) > totalWidth) { - _this.scrollTo(0, totalWidth - parentWidth, _this.bounceTime); + _this.scrollTo(-(totalWidth - parentWidth), 0, _this.bounceTime); + return; } if(_this.y > 0) { @@ -3325,24 +3337,32 @@ ionic.views.Scroll.prototype = { }, _move: function (e) { + // Make sure scrolling is enabled if ( !this.enabled || utils.eventType[e.type] !== this.initiated ) { return; } + // Prevent default if necessary if ( this.options.preventDefault ) { // increases performance on Android? TODO: check! e.preventDefault(); } + // Grab the current touch point var point = e.touches ? e.touches[0] : e, + // Check how far we've scrolled in the X direction deltaX = this.hasHorizontalScroll ? point.pageX - this.pointX : 0, + // Check how far we've scrolled in the Y direction deltaY = this.hasVerticalScroll ? point.pageY - this.pointY : 0, + // Get current timestmap timestamp = utils.getTime(), newX, newY, absDistX, absDistY; + // Store the current finger points this.pointX = point.pageX; this.pointY = point.pageY; + // Calculate the total distance travelled this.distX += deltaX; this.distY += deltaY; absDistX = Math.abs(this.distX); @@ -3384,10 +3404,12 @@ ionic.views.Scroll.prototype = { deltaX = 0; } + // The new scroll point is the current position plus the delta moved since + // last newX = this.x + deltaX; newY = this.y + deltaY; - // Slow down if outside of the boundaries + // Slow down if outside of the boundaries (rubber banding) if ( newX > 0 || newX < this.maxScrollX ) { newX = this.options.bounce ? this.x + deltaX / 3 : newX > 0 ? 0 : this.maxScrollX; } @@ -3395,13 +3417,18 @@ ionic.views.Scroll.prototype = { newY = this.options.bounce ? this.y + deltaY / 3 : newY > 0 ? 0 : this.maxScrollY; } + // Calcualte the directional multiplier this.directionX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0; this.directionY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0; + // We moved this.moved = true; + // Translate to the new position this._translate(newX, newY); + // Check if the difference between right now and when we started is bigger than 300ms. + // We then reset the start positions to reduce the possibility of large errors if ( timestamp - this.startTime > 300 ) { this.startTime = timestamp; this.startX = this.x; @@ -3411,20 +3438,26 @@ ionic.views.Scroll.prototype = { }, _end: function (e) { + // Check if scrolling is enabled if ( !this.enabled || utils.eventType[e.type] !== this.initiated ) { return; } + // Prevent default if enabled if ( this.options.preventDefault && !utils.preventDefaultException(e.target, this.options.preventDefaultException) ) { e.preventDefault(); } + // Get the current point var point = e.changedTouches ? e.changedTouches[0] : e, momentumX, momentumY, + // Calculate how long we've been dragging for time wise duration = utils.getTime() - this.startTime, + // Store the new position rounded newX = Math.round(this.x), newY = Math.round(this.y), + // Calculate the total distance travelled distanceX = Math.abs(newX - this.startX), distanceY = Math.abs(newY - this.startY), time = 0, diff --git a/js/views/scrollView.js b/js/views/scrollView.js index d66acd21ac..6b03deff6d 100644 --- a/js/views/scrollView.js +++ b/js/views/scrollView.js @@ -131,7 +131,7 @@ } */ - console.log('Momentum of time', time, speed, destination, duration); + console.log('Momentum: time:', time, 'speed:',speed, 'dest:',destination, 'dur:',duration); return { destination: Math.round(destination), duration: duration @@ -318,7 +318,6 @@ newY = 0; } - console.log(newX, newY); // Update the new translated Y point of the container _this.el.style.webkitTransform = 'translate3d(' + newX + 'px,' + newY + 'px, 0)'; @@ -326,6 +325,8 @@ _this.x = newX; _this.y = newY; + console.log('Moving to', newX, newY); + // Check if we need to reset the drag initial states if we've // been dragging for a bit if(timestamp - drag.startTime > 300) { @@ -379,30 +380,41 @@ var parentHeight = _this.el.offsetHeight; // Calculate how long we've been dragging for, with a max of 300ms - var duration = Math.min(300, (Date.now()) - _this._drag.startTime); + var duration = Date.now() - _this._drag.startTime; var newX = Math.round(_this.x); var newY = Math.round(_this.y); + + this.scrollTo(newX, newY); + + var distanceX = Math.abs(newX - _this.startX); + var distanceY = Math.abs(newY - _this.startY); + this.endTime = Date.now(); + //distanceX = Math.abs(newX - this.startX), //var distanceY = Math.abs(newY - drag.startY); + + // If the duration is within reasonable bounds, enable momentum scrolling + if(duration < 300) { + var momentumX = _this._getMomentum(_this.x, drag.startX, duration, parentWidth - totalWidth, parentWidth); + var momentumY = _this._getMomentum(_this.y, drag.startY, duration, parentHeight - totalHeight, parentHeight); + //var newX = momentumX.destination; + newX = momentumX.destination; + newY = momentumY.destination; - - var momentumX = _this._getMomentum(_this.x, drag.startX, duration, parentWidth - totalWidth, parentWidth); - var momentumY = _this._getMomentum(_this.y, drag.startY, duration, parentHeight - totalHeight, parentHeight); - //var newX = momentumX.destination; - newX = momentumX.destination; - newY = momentumY.destination; - - var timeX = momentumX.duration; - var timeY = momentumY.duration; + var timeX = momentumX.duration; + var timeY = momentumY.duration; + } // Check if we need to rubber band back if(_this.x > 0) { _this.scrollTo(0, 0, _this.bounceTime); + return; } else if((-_this.x + parentWidth) > totalWidth) { - _this.scrollTo(0, totalWidth - parentWidth, _this.bounceTime); + _this.scrollTo(-(totalWidth - parentWidth), 0, _this.bounceTime); + return; } if(_this.y > 0) { diff --git a/js/views/scrollerView.js b/js/views/scrollerView.js index 1480839e95..0639aca526 100644 --- a/js/views/scrollerView.js +++ b/js/views/scrollerView.js @@ -416,24 +416,32 @@ ionic.views.Scroll.prototype = { }, _move: function (e) { + // Make sure scrolling is enabled if ( !this.enabled || utils.eventType[e.type] !== this.initiated ) { return; } + // Prevent default if necessary if ( this.options.preventDefault ) { // increases performance on Android? TODO: check! e.preventDefault(); } + // Grab the current touch point var point = e.touches ? e.touches[0] : e, + // Check how far we've scrolled in the X direction deltaX = this.hasHorizontalScroll ? point.pageX - this.pointX : 0, + // Check how far we've scrolled in the Y direction deltaY = this.hasVerticalScroll ? point.pageY - this.pointY : 0, + // Get current timestmap timestamp = utils.getTime(), newX, newY, absDistX, absDistY; + // Store the current finger points this.pointX = point.pageX; this.pointY = point.pageY; + // Calculate the total distance travelled this.distX += deltaX; this.distY += deltaY; absDistX = Math.abs(this.distX); @@ -475,10 +483,12 @@ ionic.views.Scroll.prototype = { deltaX = 0; } + // The new scroll point is the current position plus the delta moved since + // last newX = this.x + deltaX; newY = this.y + deltaY; - // Slow down if outside of the boundaries + // Slow down if outside of the boundaries (rubber banding) if ( newX > 0 || newX < this.maxScrollX ) { newX = this.options.bounce ? this.x + deltaX / 3 : newX > 0 ? 0 : this.maxScrollX; } @@ -486,13 +496,18 @@ ionic.views.Scroll.prototype = { newY = this.options.bounce ? this.y + deltaY / 3 : newY > 0 ? 0 : this.maxScrollY; } + // Calcualte the directional multiplier this.directionX = deltaX > 0 ? -1 : deltaX < 0 ? 1 : 0; this.directionY = deltaY > 0 ? -1 : deltaY < 0 ? 1 : 0; + // We moved this.moved = true; + // Translate to the new position this._translate(newX, newY); + // Check if the difference between right now and when we started is bigger than 300ms. + // We then reset the start positions to reduce the possibility of large errors if ( timestamp - this.startTime > 300 ) { this.startTime = timestamp; this.startX = this.x; @@ -502,20 +517,26 @@ ionic.views.Scroll.prototype = { }, _end: function (e) { + // Check if scrolling is enabled if ( !this.enabled || utils.eventType[e.type] !== this.initiated ) { return; } + // Prevent default if enabled if ( this.options.preventDefault && !utils.preventDefaultException(e.target, this.options.preventDefaultException) ) { e.preventDefault(); } + // Get the current point var point = e.changedTouches ? e.changedTouches[0] : e, momentumX, momentumY, + // Calculate how long we've been dragging for time wise duration = utils.getTime() - this.startTime, + // Store the new position rounded newX = Math.round(this.x), newY = Math.round(this.y), + // Calculate the total distance travelled distanceX = Math.abs(newX - this.startX), distanceY = Math.abs(newY - this.startY), time = 0, diff --git a/scss/ionic/_scroll.scss b/scss/ionic/_scroll.scss index 3d971807e1..416a16504d 100644 --- a/scss/ionic/_scroll.scss +++ b/scss/ionic/_scroll.scss @@ -4,5 +4,22 @@ */ .scroll { position: absolute; - width: 100%; + z-index: 1; + -webkit-tap-highlight-color: rgba(0,0,0,0); + width: 100%; + -webkit-transform: translateZ(0); + -moz-transform: translateZ(0); + -ms-transform: translateZ(0); + -o-transform: translateZ(0); + transform: translateZ(0); + -webkit-touch-callout: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + -webkit-text-size-adjust: none; + -moz-text-size-adjust: none; + -ms-text-size-adjust: none; + -o-text-size-adjust: none; + text-size-adjust: none; } diff --git a/test/scroll_forms.html b/test/scroll_forms.html index 53ac0bea8b..fdac30d79d 100644 --- a/test/scroll_forms.html +++ b/test/scroll_forms.html @@ -17,7 +17,14 @@ #wrapper { position: relative; overflow: hidden; - height: 100%; + position: absolute; + z-index: 1; + top: 44px; + left: 0; + bottom: 0; + width: 100%; + background: #ccc; + overflow: hidden; } @@ -28,7 +35,7 @@

Scroll Me

Delete -
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
@@ -70,7 +197,7 @@ diff --git a/test/scroll_horiz.html b/test/scroll_horiz.html index 4097762471..bcb3b4af60 100644 --- a/test/scroll_horiz.html +++ b/test/scroll_horiz.html @@ -54,7 +54,7 @@ var scroll = new ionic.views.ScrollView({ el: s, isHorizontalEnabled: true, - isVerticalEnabled: true + isVerticalEnabled: false });