diff --git a/dist/css/ionic.css b/dist/css/ionic.css index 2781e772de..ccecfa004d 100644 --- a/dist/css/ionic.css +++ b/dist/css/ionic.css @@ -2572,13 +2572,14 @@ body, .ionic-body { overflow: hidden; -webkit-backface-visibility: hidden; margin-top: -1px; - width: auto; } + width: auto; + height: auto; } .scroll-view { overflow: hidden; + position: relative; -webkit-backface-visibility: hidden; - margin-top: -1px; - width: auto; } + margin-top: -1px; } /** * Scroll is the scroll view component available for complex and custom @@ -2602,6 +2603,31 @@ body, .ionic-body { white-space: nowrap; -webkit-backface-visibility: hidden; } +.scroll-bar { + position: absolute; + z-index: 9999; } + +.scroll-bar-h { + height: 3px; + left: 2px; + right: 2px; + bottom: 0; } + .scroll-bar-h .scroll-bar-indicator { + height: 100%; } + +.scroll-bar-v { + width: 3px; + bottom: 2px; + top: 2px; + right: 1px; } + .scroll-bar-v .scroll-bar-indicator { + width: 100%; } + +.scroll-bar-indicator { + position: absolute; + background: rgba(0, 0, 0, 0.3); + border-radius: 2px; } + .scroll-refresher { overflow: hidden; margin-top: -60px; diff --git a/dist/js/ionic-angular.js b/dist/js/ionic-angular.js index 478639091b..f798adbcd2 100644 --- a/dist/js/ionic-angular.js +++ b/dist/js/ionic-angular.js @@ -657,27 +657,20 @@ angular.module('ionic.ui.content', []) scroll: '@', hasScrollX: '@', hasScrollY: '@', + scrollbarX: '@', + scrollbarY: '@', scrollEventInterval: '@' }, compile: function(element, attr, transclude) { return function($scope, $element, $attr) { - var clone, sc, sv; + var clone, sc, sv, + addedPadding = false, + c = $element.eq(0); - var addedPadding = false; - var c = $element.eq(0); - - if(attr.hasHeader == "true") { - c.addClass('has-header'); - } - if(attr.hasSubheader == "true") { - c.addClass('has-subheader'); - } - if(attr.hasFooter == "true") { - c.addClass('has-footer'); - } - if(attr.hasTabs == "true") { - c.addClass('has-tabs'); - } + if(attr.hasHeader == "true") { c.addClass('has-header'); } + if(attr.hasSubheader == "true") { c.addClass('has-subheader'); } + if(attr.hasFooter == "true") { c.addClass('has-footer'); } + if(attr.hasTabs == "true") { c.addClass('has-tabs'); } // If they want plain overflow scrolling, add that as a class if($scope.scroll === "false") { @@ -719,6 +712,10 @@ angular.module('ionic.ui.content', []) $timeout(function() { sv = new ionic.views.Scroll({ el: $element[0], + scrollbarX: $scope.$eval($scope.scrollbarX) !== false, + scrollbarY: $scope.$eval($scope.scrollbarY) !== false, + scrollingX: $scope.$eval($scope.hasScrollX) == true, + scrollingY: $scope.$eval($scope.hasScrollY) !== false, scrollEventInterval: parseInt($scope.scrollEventInterval, 10) || 20, scrollingComplete: function() { $scope.onScrollComplete({ @@ -1591,6 +1588,8 @@ angular.module('ionic.ui.scroll', []) onScroll: '&', refreshComplete: '=', scroll: '@', + scrollbarX: '@', + scrollbarY: '@', }, compile: function(element, attr, transclude) { @@ -1630,6 +1629,8 @@ angular.module('ionic.ui.scroll', []) sv = new ionic.views.Scroll({ el: $element[0], + scrollbarX: $scope.$eval($scope.scrollbarX) !== false, + scrollbarY: $scope.$eval($scope.scrollbarY) !== false, scrollingX: hasScrollingX, scrollingY: hasScrollingY }); diff --git a/dist/js/ionic.js b/dist/js/ionic.js index 42421e7a2c..4f52b0a966 100644 --- a/dist/js/ionic.js +++ b/dist/js/ionic.js @@ -2360,9 +2360,15 @@ ionic.views.Scroll = ionic.views.View.inherit({ /** Disable scrolling on x-axis by default */ scrollingX: false, + scrollbarX: true, /** Enable scrolling on y-axis */ scrollingY: true, + scrollbarY: true, + + /** The minimum size the scrollbars scale to while scrolling */ + minScrollbarSizeX: 5, + minScrollbarSizeY: 5, /** Enable animations for deceleration, snap back, zooming and scrolling */ animating: true, @@ -2437,6 +2443,7 @@ ionic.views.Scroll = ionic.views.View.inherit({ // and calculate the size of the scroll container this.__callback = this.getRenderFn(); this.__initEventHandlers(); + this.__createScrollbars(); this.resize(); }, @@ -2599,6 +2606,15 @@ ionic.views.Scroll = ionic.views.View.inherit({ __decelerationVelocityY: null, + /** {String} the browser-specific property to use for transforms */ + __transformProperty: null, + __perspectiveProperty: null, + + /** {Object} scrollbar indicators */ + __indicatorX: null, + __indicatorY: null, + + __initEventHandlers: function() { var self = this; @@ -2672,6 +2688,160 @@ ionic.views.Scroll = ionic.views.View.inherit({ } }, + /** Create a scroll bar div with the given direction **/ + __createScrollbar: function(direction) { + var bar = document.createElement('div'), + indicator = document.createElement('div'); + + indicator.className = 'scroll-bar-indicator'; + + if(direction == 'h') { + bar.className = 'scroll-bar scroll-bar-h'; + } else { + bar.className = 'scroll-bar scroll-bar-v'; + } + + bar.appendChild(indicator); + return bar; + }, + + __createScrollbars: function() { + var indicatorX, indicatorY; + + if(this.options.scrollingX) { + indicatorX = { + el: this.__createScrollbar('h'), + sizeRatio: 1 + }; + indicatorX.indicator = indicatorX.el.children[0]; + + if(this.options.scrollbarX) { + this.__container.appendChild(indicatorX.el); + } + this.__indicatorX = indicatorX; + } + + if(this.options.scrollingY) { + indicatorY = { + el: this.__createScrollbar('v'), + sizeRatio: 1 + }; + indicatorY.indicator = indicatorY.el.children[0]; + + if(this.options.scrollbarY) { + this.__container.appendChild(indicatorY.el); + } + this.__indicatorY = indicatorY; + } + }, + + __resizeScrollbars: function() { + var self = this; + + // Update horiz bar + if(self.__indicatorX) { + var width = Math.max(Math.round(self.__clientWidth * self.__clientWidth / (self.__contentWidth)), 20); + self.__indicatorX.size = width; + self.__indicatorX.minScale = this.options.minScrollbarSizeX / width; + self.__indicatorX.indicator.style.width = width + 'px'; + self.__indicatorX.maxPos = self.__clientWidth - width; + self.__indicatorX.sizeRatio = self.__maxScrollLeft ? self.__indicatorX.maxPos / self.__maxScrollLeft : 1; + } + + // Update vert bar + if(self.__indicatorY) { + var height = Math.max(Math.round(self.__clientHeight * self.__clientHeight / (self.__contentHeight)), 20); + self.__indicatorY.size = height; + self.__indicatorY.minScale = this.options.minScrollbarSizeY / height; + self.__indicatorY.maxPos = self.__clientHeight - height; + self.__indicatorY.indicator.style.height = height + 'px'; + self.__indicatorY.sizeRatio = self.__maxScrollTop ? self.__indicatorY.maxPos / self.__maxScrollTop : 1; + } + }, + + /** + * Move and scale the scrollbars as the page scrolls. + */ + __repositionScrollbars: function() { + var self = this, width, heightScale, widthDiff, heightDiff, x, y; + + if(self.__indicatorX) { + // Handle the X scrollbar + + x = Math.round(self.__indicatorX.sizeRatio * self.__scrollLeft) || 0, + + // The the difference between the last content X position, and our overscrolled one + widthDiff = self.__scrollLeft - self.__maxScrollLeft; + + if(self.__scrollLeft < 0) { + + widthScale = Math.max(self.__indicatorX.minScale, + (self.__indicatorX.size - Math.abs(self.__scrollLeft)) / self.__indicatorX.size); + + // Stay at left + x = 0; + + // Make sure scale is transformed from the left/center origin point + self.__indicatorX.indicator.style[self.__transformOriginProperty] = 'left center'; + } else if(widthDiff > 0) { + + widthScale = Math.max(self.__indicatorX.minScale, + (self.__indicatorX.size - widthDiff) / self.__indicatorX.size); + + // Stay at the furthest x for the scrollable viewport + x = self.__indicatorX.maxPos; + + // Make sure scale is transformed from the right/center origin point + self.__indicatorX.indicator.style[self.__transformOriginProperty] = 'right center'; + + } else { + + // Normal motion + x = Math.min(self.__maxScrollLeft, Math.max(0, x)); + widthScale = 1; + + } + + self.__indicatorX.indicator.style[self.__transformProperty] = 'translate3d(' + x + 'px, 0, 0) scaleX(' + widthScale + ')'; + } + + if(self.__indicatorY) { + + y = Math.round(self.__indicatorY.sizeRatio * self.__scrollTop) || 0; + + heightDiff = self.__scrollTop - self.__maxScrollTop; + + if(self.__scrollTop < 0) { + + heightScale = Math.max(self.__indicatorY.minScale, (self.__indicatorY.size - Math.abs(self.__scrollTop)) / self.__indicatorY.size); + + // Stay at top + y = 0; + + // Make sure scale is transformed from the center/top origin point + self.__indicatorY.indicator.style[self.__transformOriginProperty] = 'center top'; + + } else if(heightDiff > 0) { + + heightScale = Math.max(self.__indicatorY.minScale, (self.__indicatorY.size - heightDiff) / self.__indicatorY.size); + + // Stay at bottom of scrollable viewport + y = self.__indicatorY.maxPos; + + // Make sure scale is transformed from the center/bottom origin point + self.__indicatorY.indicator.style[self.__transformOriginProperty] = 'center bottom'; + + } else { + + // Normal motion + y = Math.min(self.__maxScrollTop, Math.max(0, y)); + heightScale = 1; + + } + + self.__indicatorY.indicator.style[self.__transformProperty] = 'translate3d(0,' + y + 'px, 0) scaleY(' + heightScale + ')'; + } + }, resize: function() { // Update Scroller dimensions for changed content @@ -2679,8 +2849,8 @@ ionic.views.Scroll = ionic.views.View.inherit({ this.setDimensions( this.__container.clientWidth, this.__container.clientHeight, - this.__content.offsetWidth, - this.__content.offsetHeight+20); + Math.max(this.__content.scrollWidth, this.__content.offsetWidth), + Math.max(this.__content.scrollHeight, this.__content.offsetHeight+20)); }, /* --------------------------------------------------------------------------- @@ -2716,19 +2886,26 @@ ionic.views.Scroll = ionic.views.View.inherit({ var perspectiveProperty = vendorPrefix + "Perspective"; var transformProperty = vendorPrefix + "Transform"; + var transformOriginProperty = vendorPrefix + 'TransformOrigin'; + + self.__perspectiveProperty = transformProperty; + self.__transformProperty = transformProperty; + self.__transformOriginProperty = transformOriginProperty; if (helperElem.style[perspectiveProperty] !== undef) { return function(left, top, zoom) { content.style[transformProperty] = 'translate3d(' + (-left) + 'px,' + (-top) + 'px,0)'; - self.triggerScrollEvent(); + self.__repositionScrollbars(); + self.triggerScrollEvent(); }; } else if (helperElem.style[transformProperty] !== undef) { return function(left, top, zoom) { content.style[transformProperty] = 'translate(' + (-left) + 'px,' + (-top) + 'px)'; - self.triggerScrollEvent(); + self.__repositionScrollbars(); + self.triggerScrollEvent(); }; } else { @@ -2737,6 +2914,7 @@ ionic.views.Scroll = ionic.views.View.inherit({ content.style.marginLeft = left ? (-left/zoom) + 'px' : ''; content.style.marginTop = top ? (-top/zoom) + 'px' : ''; content.style.zoom = zoom || ''; + self.__repositionScrollbars(); self.triggerScrollEvent(); }; @@ -2777,6 +2955,7 @@ ionic.views.Scroll = ionic.views.View.inherit({ // Refresh maximums self.__computeScrollMax(); + self.__resizeScrollbars(); // Refresh scroll position self.scrollTo(self.__scrollLeft, self.__scrollTop, true); diff --git a/js/ext/angular/src/directive/ionicContent.js b/js/ext/angular/src/directive/ionicContent.js index da28628895..1ea68c8e41 100644 --- a/js/ext/angular/src/directive/ionicContent.js +++ b/js/ext/angular/src/directive/ionicContent.js @@ -33,27 +33,20 @@ angular.module('ionic.ui.content', []) scroll: '@', hasScrollX: '@', hasScrollY: '@', + scrollbarX: '@', + scrollbarY: '@', scrollEventInterval: '@' }, compile: function(element, attr, transclude) { return function($scope, $element, $attr) { - var clone, sc, sv; + var clone, sc, sv, + addedPadding = false, + c = $element.eq(0); - var addedPadding = false; - var c = $element.eq(0); - - if(attr.hasHeader == "true") { - c.addClass('has-header'); - } - if(attr.hasSubheader == "true") { - c.addClass('has-subheader'); - } - if(attr.hasFooter == "true") { - c.addClass('has-footer'); - } - if(attr.hasTabs == "true") { - c.addClass('has-tabs'); - } + if(attr.hasHeader == "true") { c.addClass('has-header'); } + if(attr.hasSubheader == "true") { c.addClass('has-subheader'); } + if(attr.hasFooter == "true") { c.addClass('has-footer'); } + if(attr.hasTabs == "true") { c.addClass('has-tabs'); } // If they want plain overflow scrolling, add that as a class if($scope.scroll === "false") { @@ -95,6 +88,10 @@ angular.module('ionic.ui.content', []) $timeout(function() { sv = new ionic.views.Scroll({ el: $element[0], + scrollbarX: $scope.$eval($scope.scrollbarX) !== false, + scrollbarY: $scope.$eval($scope.scrollbarY) !== false, + scrollingX: $scope.$eval($scope.hasScrollX) == true, + scrollingY: $scope.$eval($scope.hasScrollY) !== false, scrollEventInterval: parseInt($scope.scrollEventInterval, 10) || 20, scrollingComplete: function() { $scope.onScrollComplete({ diff --git a/js/ext/angular/src/directive/ionicScroll.js b/js/ext/angular/src/directive/ionicScroll.js index 7b63a85ff9..2413d13585 100644 --- a/js/ext/angular/src/directive/ionicScroll.js +++ b/js/ext/angular/src/directive/ionicScroll.js @@ -15,6 +15,8 @@ angular.module('ionic.ui.scroll', []) onScroll: '&', refreshComplete: '=', scroll: '@', + scrollbarX: '@', + scrollbarY: '@', }, compile: function(element, attr, transclude) { @@ -54,6 +56,8 @@ angular.module('ionic.ui.scroll', []) sv = new ionic.views.Scroll({ el: $element[0], + scrollbarX: $scope.$eval($scope.scrollbarX) !== false, + scrollbarY: $scope.$eval($scope.scrollbarY) !== false, scrollingX: hasScrollingX, scrollingY: hasScrollingY }); diff --git a/js/ext/angular/test/content.html b/js/ext/angular/test/content.html index f864d9414f..6efd2e6a4d 100644 --- a/js/ext/angular/test/content.html +++ b/js/ext/angular/test/content.html @@ -67,12 +67,10 @@ has-tabs="true" has-header="true" > - - - +