Skip to content

Commit aacdba8

Browse files
authored
Merge pull request #46 from lfalin/anchor-fix
Fix anchor scrolling that hides behind top nav bar
2 parents bf4cf2e + 0e61c86 commit aacdba8

File tree

1 file changed

+88
-0
lines changed

1 file changed

+88
-0
lines changed

static/js/learn.js

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,94 @@ jQuery(document).ready(function() {
225225
$('.progress').hover(function() {
226226
$('.progress').stop(true, false, true).fadeToggle(100);
227227
});
228+
229+
/**
230+
* Fix anchor scrolling that hides behind top nav bar
231+
* Courtesy of https://stackoverflow.com/a/13067009/28106
232+
*
233+
* We could use pure css for this if only heading anchors were
234+
* involved, but this works for any anchor, including footnotes
235+
**/
236+
(function(document, history, location) {
237+
var HISTORY_SUPPORT = !!(history && history.pushState);
238+
239+
var anchorScrolls = {
240+
ANCHOR_REGEX: /^#[^ ]+$/,
241+
OFFSET_HEIGHT_PX: 50,
242+
243+
/**
244+
* Establish events, and fix initial scroll position if a hash is provided.
245+
*/
246+
init: function() {
247+
this.scrollToCurrent();
248+
window.addEventListener('hashchange', this.scrollToCurrent.bind(this));
249+
document.body.addEventListener('click', this.delegateAnchors.bind(this));
250+
},
251+
252+
/**
253+
* Return the offset amount to deduct from the normal scroll position.
254+
* Modify as appropriate to allow for dynamic calculations
255+
*/
256+
getFixedOffset: function() {
257+
return this.OFFSET_HEIGHT_PX;
258+
},
259+
260+
/**
261+
* If the provided href is an anchor which resolves to an element on the
262+
* page, scroll to it.
263+
* @param {String} href
264+
* @return {Boolean} - Was the href an anchor.
265+
*/
266+
scrollIfAnchor: function(href, pushToHistory) {
267+
var match, rect, anchorOffset;
268+
269+
if(!this.ANCHOR_REGEX.test(href)) {
270+
return false;
271+
}
272+
273+
match = document.getElementById(href.slice(1));
274+
275+
if(match) {
276+
rect = match.getBoundingClientRect();
277+
anchorOffset = window.pageYOffset + rect.top - this.getFixedOffset();
278+
window.scrollTo(window.pageXOffset, anchorOffset);
279+
280+
// Add the state to history as-per normal anchor links
281+
if(HISTORY_SUPPORT && pushToHistory) {
282+
history.pushState({}, document.title, location.pathname + href);
283+
}
284+
}
285+
286+
return !!match;
287+
},
288+
289+
/**
290+
* Attempt to scroll to the current location's hash.
291+
*/
292+
scrollToCurrent: function() {
293+
this.scrollIfAnchor(window.location.hash);
294+
},
295+
296+
/**
297+
* If the click event's target was an anchor, fix the scroll position.
298+
*/
299+
delegateAnchors: function(e) {
300+
var elem = e.target;
301+
302+
if(
303+
elem.nodeName === 'A' &&
304+
this.scrollIfAnchor(elem.getAttribute('href'), true)
305+
) {
306+
e.preventDefault();
307+
}
308+
}
309+
};
310+
311+
window.addEventListener(
312+
'DOMContentLoaded', anchorScrolls.init.bind(anchorScrolls)
313+
);
314+
})(window.document, window.history, window.location);
315+
228316
});
229317

230318
jQuery(window).on('load', function() {

0 commit comments

Comments
 (0)