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"
>
-