From 05c64bde9b5081a1cf0733df0bba70aa077b4f44 Mon Sep 17 00:00:00 2001 From: lfalin Date: Sat, 27 May 2017 18:27:37 -0600 Subject: [PATCH] Fix anchor scrolling that hides behind top nav bar --- static/js/learn.js | 88 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/static/js/learn.js b/static/js/learn.js index b776ac7ba53..856c3320b8b 100644 --- a/static/js/learn.js +++ b/static/js/learn.js @@ -225,6 +225,94 @@ jQuery(document).ready(function() { $('.progress').hover(function() { $('.progress').stop(true, false, true).fadeToggle(100); }); + + /** + * Fix anchor scrolling that hides behind top nav bar + * Courtesy of https://stackoverflow.com/a/13067009/28106 + * + * We could use pure css for this if only heading anchors were + * involved, but this works for any anchor, including footnotes + **/ + (function(document, history, location) { + var HISTORY_SUPPORT = !!(history && history.pushState); + + var anchorScrolls = { + ANCHOR_REGEX: /^#[^ ]+$/, + OFFSET_HEIGHT_PX: 50, + + /** + * Establish events, and fix initial scroll position if a hash is provided. + */ + init: function() { + this.scrollToCurrent(); + window.addEventListener('hashchange', this.scrollToCurrent.bind(this)); + document.body.addEventListener('click', this.delegateAnchors.bind(this)); + }, + + /** + * Return the offset amount to deduct from the normal scroll position. + * Modify as appropriate to allow for dynamic calculations + */ + getFixedOffset: function() { + return this.OFFSET_HEIGHT_PX; + }, + + /** + * If the provided href is an anchor which resolves to an element on the + * page, scroll to it. + * @param {String} href + * @return {Boolean} - Was the href an anchor. + */ + scrollIfAnchor: function(href, pushToHistory) { + var match, rect, anchorOffset; + + if(!this.ANCHOR_REGEX.test(href)) { + return false; + } + + match = document.getElementById(href.slice(1)); + + if(match) { + rect = match.getBoundingClientRect(); + anchorOffset = window.pageYOffset + rect.top - this.getFixedOffset(); + window.scrollTo(window.pageXOffset, anchorOffset); + + // Add the state to history as-per normal anchor links + if(HISTORY_SUPPORT && pushToHistory) { + history.pushState({}, document.title, location.pathname + href); + } + } + + return !!match; + }, + + /** + * Attempt to scroll to the current location's hash. + */ + scrollToCurrent: function() { + this.scrollIfAnchor(window.location.hash); + }, + + /** + * If the click event's target was an anchor, fix the scroll position. + */ + delegateAnchors: function(e) { + var elem = e.target; + + if( + elem.nodeName === 'A' && + this.scrollIfAnchor(elem.getAttribute('href'), true) + ) { + e.preventDefault(); + } + } + }; + + window.addEventListener( + 'DOMContentLoaded', anchorScrolls.init.bind(anchorScrolls) + ); + })(window.document, window.history, window.location); + }); jQuery(window).on('load', function() {