33
33
.controller('ScrollController', ['$scope', '$location', '$anchorScroll',
34
34
function ($scope, $location, $anchorScroll) {
35
35
$scope.gotoBottom = function() {
36
- // set the location.hash to the id of
37
- // the element you wish to scroll to .
36
+ // Set the location.hash to the id of the element you wish to scroll to
37
+ // and $anchorScroll will kick in automatically .
38
38
$location.hash('bottom');
39
-
40
- // call $anchorScroll()
41
- $anchorScroll();
42
39
};
43
40
}]);
44
41
</file>
72
69
</file>
73
70
<file name="script.js">
74
71
angular.module('anchorScrollOffsetExample', [])
75
- .config (['$anchorScrollProvider ', function($anchorScrollProvider ) {
76
- $anchorScrollProvider.setScrollOffset(50) ; // always scroll by 50 extra pixels
72
+ .run (['$anchorScroll ', function($anchorScroll ) {
73
+ $anchorScroll.yOffset = 50 ; // always scroll by 50 extra pixels
77
74
}])
78
75
.controller('headerCtrl', ['$anchorScroll', '$location', '$scope',
79
76
function ($anchorScroll, $location, $scope) {
80
77
$scope.gotoAnchor = function(x) {
81
- // Set the location.hash to the id of
82
- // the element you wish to scroll to .
78
+ // Set the location.hash to the id of the element you wish to scroll to
79
+ // and $anchorScroll will kick in automatically .
83
80
$location.hash('anchor' + x);
84
-
85
- // Call $anchorScroll()
86
- $anchorScroll();
87
81
};
88
82
}
89
83
]);
113
107
</example>
114
108
*/
115
109
function $AnchorScrollProvider ( ) {
116
- // TODO(gkalpak): The $anchorScrollProvider should be documented as well
117
- // (under the providers section).
118
110
119
111
var autoScrollingEnabled = true ;
120
112
@@ -125,9 +117,38 @@ function $AnchorScrollProvider() {
125
117
this . $get = [ '$window' , '$location' , '$rootScope' , function ( $window , $location , $rootScope ) {
126
118
var document = $window . document ;
127
119
128
- // Helper function to get first anchor from a NodeList
129
- // (using `Array#some()` instead of `angular#forEach()` since it's more performant
130
- // and working in all supported browsers.)
120
+ // Helper function to increase code readability and DRYness
121
+ function asInt ( val ) {
122
+ return parseInt ( val , 10 ) || 0 ;
123
+ }
124
+
125
+ // IE8 does not support `window.getComputedStyle()`, but it's proprietary `elem.currentStyle`
126
+ // seems to work the same. If neither exists, fall back to `elem.style`.
127
+ function computedStyleGetter ( elem ) {
128
+ return $window . getComputedStyle ?
129
+ $window . getComputedStyle ( elem ) :
130
+ elem . currentStyle || elem . style || { } ;
131
+ }
132
+
133
+ // `offsetTop` works consistently across browsers, but returns the offset from the element's
134
+ // "offsetParent", which is (either <body> or the first ancestor with `position !== 'static'` or
135
+ // (if elem has `position: static`) a <table>, <th>, <td>).
136
+ // This function will recursively add the offsetTop of each offsetParent along with the width of
137
+ // its top-border.
138
+ function offsetTopGetter ( elem ) {
139
+ var totalOffsetTop = asInt ( elem . offsetTop ) ;
140
+
141
+ while ( elem = elem . offsetParent ) {
142
+ var computedStyle = computedStyleGetter ( elem ) ;
143
+ var borderTop = asInt ( computedStyle . borderTopWidth ) ;
144
+ var offsetTop = asInt ( elem . offsetTop ) ;
145
+ totalOffsetTop += offsetTop + borderTop ;
146
+ }
147
+
148
+ return totalOffsetTop ;
149
+ }
150
+
151
+
131
152
function getFirstAnchor ( list ) {
132
153
var result = null ;
133
154
Array . prototype . some . call ( list , function ( element ) {
@@ -144,28 +165,47 @@ function $AnchorScrollProvider() {
144
165
var offset = scroll . yOffset ;
145
166
146
167
if ( isElement ( offset ) ) {
147
-
148
- var style = $window . getComputedStyle ( scroll . yOffset [ 0 ] ) ;
149
- var top = parseInt ( style . top , 10 ) ;
150
- var height = parseInt ( style . height , 10 ) ;
151
- return style . position === 'fixed' ? ( top + height ) : 0 ;
152
-
168
+ var elem = offset [ 0 ] ;
169
+
170
+ // TODO: Document that yOffset must be a jqLite/jQuery wrapped element
171
+ var style = computedStyleGetter ( elem ) ;
172
+ if ( style . position !== 'fixed' ) {
173
+ offset = 0 ;
174
+ } else {
175
+ // TODO: Make sure this works well on all supported browsers.
176
+ // Tested on Chrome 37, Firefox 32.0.3 and IE8-IE11 and works as expected.
177
+ // I.e. `elem.offsetHeight` consistently accounts for the height of the content,
178
+ // the padding, the scrollbars (if any) and the border of the element
179
+ // (for both `content-box` and `border-box` boxSizings).
180
+ // (See also this fiddle: http://jsfiddle.net/ExpertSystem/5dx7fg2r/
181
+ var top = offsetTopGetter ( elem . offsetTop ) ;
182
+ var height = asInt ( elem . offsetHeight ) ;
183
+ offset = top + height ;
184
+ }
153
185
} else if ( isFunction ( offset ) ) {
154
- return offset ( ) ;
155
-
156
- } else if ( isNumber ( offset ) ) {
157
- return offset ;
158
-
159
- } else {
160
- return 0 ;
186
+ offset = asInt ( offset ( ) ) ;
187
+ } else if ( ! isNumber ( offset ) ) {
188
+ offset = 0 ;
161
189
}
162
190
191
+ return offset ;
163
192
}
164
193
165
194
function scrollTo ( elem ) {
166
195
if ( elem ) {
167
196
elem . scrollIntoView ( ) ;
168
- $window . scrollBy ( 0 , - 1 * getYOffset ( ) ) ;
197
+
198
+ var offset = getYOffset ( ) ;
199
+ // `offset` is the number of pixels we should scroll up in order to align `elem` properly.
200
+ // This is true ONLY if the call to `elem.scrollIntoView()` initially aligns `elem` at the
201
+ // top of the viewport. IF the number of pixels from the top of `elem` to the end of page's
202
+ // content is less than the height of the viewport, then `elem.scrollIntoView()` will NOT
203
+ // align the top of `elem` at the top of the viewport (but further down). This is often the
204
+ // case for elements near the bottom of the page.
205
+ // In such cases we do not need to scroll the whole `offset` up, just the fraction of the
206
+ // offset that is necessary to align the top of `elem` at the desired position.
207
+ var necessaryOffset = offset && ( offset - ( offsetTopGetter ( elem ) - document . body . scrollTop ) ) ;
208
+ $window . scrollBy ( 0 , - 1 * necessaryOffset ) ;
169
209
} else {
170
210
$window . scrollTo ( 0 , 0 ) ;
171
211
}
0 commit comments