From f86567c1f22f5d4dfa9bcc097efaef3ecb8b44bc Mon Sep 17 00:00:00 2001 From: Kohei Yoshino Date: Mon, 8 Jan 2018 09:22:45 -0500 Subject: Bug 1427800 - Wrong anchor scrolling with old UI --- js/global.js | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 50 insertions(+), 8 deletions(-) (limited to 'js') diff --git a/js/global.js b/js/global.js index 93f364c9e..68aceb03f 100644 --- a/js/global.js +++ b/js/global.js @@ -214,22 +214,64 @@ const detect_blocked_gravatars = () => { } /** - * If the URL contains a hash like #c10, scroll down the page to show the - * element below the fixed global header. This workaround is required for - * comments on show_bug.cgi, components on describecomponents.cgi, etc. + * If the current URL contains a hash like `#c10`, adjust the scroll position to + * make some room above the focused element. */ -const scroll_element_into_view = () => { +const adjust_scroll_onload = () => { if (location.hash) { - const $main = document.querySelector('main'); const $target = document.querySelector(location.hash); if ($target) { - window.setTimeout(() => $main.scrollTop = $target.offsetTop - 20, 50); + window.setTimeout(() => scroll_element_into_view($target), 50); } } } +/** + * Bring an element into the visible area of the browser window. Unlike the + * native `Element.scrollIntoView()` function, this adds some extra room above + * the target element. Smooth scroll can be done using CSS. + * @param {Element} $target - An element to be brought. + * @param {Function} [complete] - An optional callback function to be executed + * once the scroll is complete. + */ +const scroll_element_into_view = ($target, complete) => { + let top = 0; + let $element = $target; + + // Traverse up in the DOM tree to the scroll container of the + // focused element, either `
` or `
`. + do { + top += ($element.offsetTop || 0); + $element = $element.offsetParent; + } while ($element && !$element.matches('main, [role="feed"]')) + + if (!$element) { + return; + } + + if (typeof complete === 'function') { + const callback = () => { + $element.removeEventListener('scroll', listener); + complete(); + }; + + // Emulate the `scrollend` event + const listener = () => { + window.clearTimeout(timer); + timer = window.setTimeout(callback, 100); + }; + + // Make sure the callback is always fired even if no scroll happened + let timer = window.setTimeout(callback, 100); + + $element.addEventListener('scroll', listener); + } + + $element.scrollTop = top - 20; +} + window.addEventListener('DOMContentLoaded', focus_main_content, { once: true }); window.addEventListener('load', detect_blocked_gravatars, { once: true }); -window.addEventListener('load', scroll_element_into_view, { once: true }); -window.addEventListener('hashchange', scroll_element_into_view); +window.addEventListener('load', adjust_scroll_onload, { once: true }); +window.addEventListener('hashchange', adjust_scroll_onload); -- cgit v1.2.3-24-g4f1b