Skip to content

Commit 88194cb

Browse files
committed
WIP - feat($anchorScroll): add support for configurable scroll offset
Add support for a configurable scroll offset to $anchorScrollProvider. The offset is expressed in pixels and can be specified either as a fixed value or as a function that return the offset it pixels dynamically. (This is a POC and a WIP: no docs, no tests) Related to angular#9368 Closes angular#2070, angular#9360
1 parent 944408e commit 88194cb

File tree

5 files changed

+132
-8
lines changed

5 files changed

+132
-8
lines changed

docs/app/src/app.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@ angular.module('docsApp', [
1313
'search',
1414
'tutorials',
1515
'versions',
16+
'scrollOffset',
1617
'bootstrap',
1718
'ui.bootstrap.dropdown'
1819
])
1920

20-
2121
.config(['$locationProvider', function($locationProvider) {
2222
$locationProvider.html5Mode(true).hashPrefix('!');
2323
}]);

docs/app/src/directives.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,3 @@ angular.module('directives', [])
2929
}
3030
};
3131
});
32-

docs/app/src/scroll-offset.js

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
angular.module('scrollOffset', [])
2+
3+
/**
4+
* scrollOffsetElement Directive
5+
*
6+
* @description Store the element whose height should be used to determine the scroll offset and its
7+
* computed style.
8+
*/
9+
.directive('scrollOffsetElement', ['$window', 'SCROLL_OFFSET_ELEMENT',
10+
function scrollOffsetElementDirective($window, SCROLL_OFFSET_ELEMENT) {
11+
return {
12+
restrict: 'A',
13+
link: function scrollOffsetElementPostLink(scope, elem) {
14+
elem = elem[0];
15+
SCROLL_OFFSET_ELEMENT.element = elem;
16+
SCROLL_OFFSET_ELEMENT.computedStyle = $window.getComputedStyle(elem);
17+
// README: Using `getComputedStyle()` renders this approach incompatible with IE8.
18+
}
19+
};
20+
}
21+
])
22+
23+
.constant('SCROLL_OFFSET_ELEMENT', {
24+
element: null, // README: This is not used, but keeping it here just in case...
25+
computedStyle: null
26+
})
27+
28+
.config(['$anchorScrollProvider', 'SCROLL_OFFSET_ELEMENT',
29+
function($anchorScrollProvider, SCROLL_OFFSET_ELEMENT) {
30+
var extraTopSpace = 15;
31+
$anchorScrollProvider.setScrollOffset(function () {
32+
var computedStyle = SCROLL_OFFSET_ELEMENT.computedStyle;
33+
34+
if (!computedStyle || (computedStyle.position !== 'fixed')) return 0;
35+
36+
return parseInt(computedStyle.height, 10) + extraTopSpace;
37+
});
38+
}
39+
]);

docs/config/templates/indexPage.template.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
</head>
7171
<body>
7272
<div id="wrapper">
73-
<header class="header header-fixed">
73+
<header class="header header-fixed" scroll-offset-element>
7474
<section class="navbar navbar-inverse docs-navbar-primary" ng-controller="DocsSearchCtrl">
7575
<div class="container">
7676
<div class="row">

src/ng/anchorScroll.js

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
* It also watches the `$location.hash()` and scrolls whenever it changes to match any anchor.
1717
* This can be disabled by calling `$anchorScrollProvider.disableAutoScrolling()`.
1818
*
19+
* Additionally, you can specify a scroll offset (in pixels) during the configuration phase by
20+
* calling `$anchorScrollProvider.setScrollOffset(<valueOrGetter>)`. The offset can be either a
21+
* fixed value or a getter function that returns a value dynamically.
22+
*
1923
* @example
2024
<example module="anchorScrollExample">
2125
<file name="index.html">
@@ -50,15 +54,85 @@
5054
}
5155
</file>
5256
</example>
57+
*
58+
* <hr />
59+
* The example below illustrates the use of scroll offset (specified as a fixed value).
60+
*
61+
* @example
62+
<example module="anchorScrollOffsetExample">
63+
<file name="index.html">
64+
<div class="fixed-header" ng-controller="headerCtrl">
65+
<a href="" ng-click="gotoAnchor(x)" ng-repeat="x in [1,2,3,4,5]">
66+
Go to anchor {{x}}
67+
</a>
68+
</div>
69+
<div id="anchor{{x}}" class="anchor" ng-repeat="x in [1,2,3,4,5]">
70+
Anchor {{x}} of 5
71+
</div>
72+
</file>
73+
<file name="script.js">
74+
angular.module('anchorScrollOffsetExample', [])
75+
.config(['$anchorScrollProvider', function($anchorScrollProvider) {
76+
$anchorScrollProvider.setScrollOffset(50); // always scroll by 50 extra pixels
77+
}])
78+
.controller('headerCtrl', ['$anchorScroll', '$location', '$scope',
79+
function ($anchorScroll, $location, $scope) {
80+
$scope.gotoAnchor = function(x) {
81+
// Set the location.hash to the id of
82+
// the element you wish to scroll to.
83+
$location.hash('anchor' + x);
84+
85+
// Call $anchorScroll()
86+
$anchorScroll();
87+
};
88+
}
89+
]);
90+
</file>
91+
<file name="style.css">
92+
body {
93+
padding-top: 50px;
94+
}
95+
96+
.anchor {
97+
border: 2px dashed DarkOrchid;
98+
padding: 10px 10px 200px 10px;
99+
}
100+
101+
.fixed-header {
102+
background-color: rgba(0, 0, 0, 0.2);
103+
height: 50px;
104+
position: fixed;
105+
top: 0; left: 0; right: 0;
106+
}
107+
108+
.fixed-header > a {
109+
display: inline-block;
110+
margin: 5px 15px;
111+
}
112+
</file>
113+
</example>
53114
*/
54115
function $AnchorScrollProvider() {
116+
// TODO(gkalpak): The $anchorScrollProvider should be documented as well
117+
// (under the providers section).
118+
119+
var DEFAULT_OFFSET = 0;
55120

56121
var autoScrollingEnabled = true;
122+
var scrollOffsetGetter = function() { return DEFAULT_OFFSET; };
57123

58124
this.disableAutoScrolling = function() {
59125
autoScrollingEnabled = false;
60126
};
61127

128+
this.setScrollOffset = function(newScrollOffset) {
129+
if (isFunction(newScrollOffset)) {
130+
scrollOffsetGetter = function() { return newScrollOffset(); };
131+
} else if (isNumber(newScrollOffset)) {
132+
scrollOffsetGetter = function() { return newScrollOffset; };
133+
}
134+
};
135+
62136
this.$get = ['$window', '$location', '$rootScope', function($window, $location, $rootScope) {
63137
var document = $window.document;
64138

@@ -76,20 +150,33 @@ function $AnchorScrollProvider() {
76150
return result;
77151
}
78152

153+
function scrollTo(elem) {
154+
if (elem) {
155+
elem.scrollIntoView();
156+
var offset = scrollOffsetGetter();
157+
var actualOffset = offset && (offset - (elem.offsetTop - document.body.scrollTop));
158+
if (actualOffset) {
159+
$window.scrollBy(0, -1 * actualOffset);
160+
}
161+
} else {
162+
$window.scrollTo(0, 0);
163+
}
164+
}
165+
79166
function scroll() {
80167
var hash = $location.hash(), elm;
81168

82169
// empty hash, scroll to the top of the page
83-
if (!hash) $window.scrollTo(0, 0);
170+
if (!hash) scrollTo(null);
84171

85172
// element with given id
86-
else if ((elm = document.getElementById(hash))) elm.scrollIntoView();
173+
else if ((elm = document.getElementById(hash))) scrollTo(elm);
87174

88175
// first anchor with given name :-D
89-
else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) elm.scrollIntoView();
176+
else if ((elm = getFirstAnchor(document.getElementsByName(hash)))) scrollTo(elm);
90177

91178
// no element and hash == 'top', scroll to the top of the page
92-
else if (hash === 'top') $window.scrollTo(0, 0);
179+
else if (hash === 'top') scrollTo(null);
93180
}
94181

95182
// does not scroll when user clicks on anchor link that is currently on
@@ -107,4 +194,3 @@ function $AnchorScrollProvider() {
107194
return scroll;
108195
}];
109196
}
110-

0 commit comments

Comments
 (0)