TLDR
On page load, if the url contains a hash, I want to distinguish between two cases with js / jQuery:
- On page refresh, or after using the back button, the browser restores the old manual scroll position and ignores the hash. In this case I want to do nothing.
- When visiting the url in a new tab, the browser jumps to the scroll position identified by the hash. In this case I want to offset by e.g. 40px to account for fixed elements.
Background / use case
I am working on a kind of "sticky menu" where clicking a link goes to the element with the id identified by the hash, except:
- The scrolling is smooth, using jQuery animate (probably better for browser compatibility than some native method).
- At the end of the scrolling, the element with the id appears some pixels (e.g. 40px) below the top end of the viewport, to not be covered by the sticky menu with position: fixed.
I am intercepting the click event with event.preventDefault() and then set the hash with history.pushState(), to prevent the native jump which would cause a screen flicker before the animation starts.
So far all works quite well. But I also want to simulate the "correct" behavior when on page load or refresh, if there is a hash.
Observation: Native browser behavior on page load
I made the following observation (using Chromium/Brave browser).
When you open a new page with a hash, the browser natively scrolls to that position. If you change the hash in the url manually, it will jump to that hash.
If you scroll to a different position while the hash is still in the url, and then refresh the page, the browser does not change the scroll position. Instead, it keeps the manual scroll position. The same happens when using the browser back button.
To simulate this behavior with smooth scroll with offset, we would need to distinguish a new page visit from a simple refresh.
The browser fires one scroll event if it just scrolls to the hash, and multiple scroll events if it then scrolls to the previous manual scroll position. But I don't know if this is reliable and consistent, or how to distinguish the different scroll events.
Question
How can we distinguish the two cases with js?
- If the browser scrolls to the previous manual scroll position on page load, keep that and don't interfere. This happens on page refresh.
- If the browser scrolls (or wants to scroll) to the position identified by the hash, apply the 40px offset and (optionally) animate the scrolling to be smooth. This happens when visiting a new page.
Alternative solution I considered
An alternative would be to modify the target elements, so that the id is on a hidden element which is 40px above the actual target. This could work on a specific site, but it seems too disruptive as a reusable solution: What if the id needs to be on the original element for other reasons?
Related notes
I came across History.scrollRestoration, which was new to me, but I don't see how it would help me with this specific problem.