Skip to content

Instantly share code, notes, and snippets.

@enwin
Last active May 17, 2022 13:11
Show Gist options
  • Save enwin/6029193e5c7e4a8b4c2bcb9345e678c0 to your computer and use it in GitHub Desktop.
Save enwin/6029193e5c7e4a8b4c2bcb9345e678c0 to your computer and use it in GitHub Desktop.
Detect and change the scroll position when a focused element is under a sticky element
// sticky element
var stickyHeader = document.querySelector( '.intro-banner' );
function handleFocus( e ){
// don't try to change the scroll if the focused element is in the sticky element
if( stickyHeader.contains( e.target )){
return;
}
// quick & dirty client height on each focus
// get the sticky element's height (can be done on mediaquery change instead of each time)
var headerHeight = stickyHeader.clientHeight;
// get the screen position of the focused element
var focusTop = e.target.getBoundingClientRect().top;
// get the current scroll
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
// scroll when the focused element is under the sticky element
if( focusTop < headerHeight ){
document.documentElement.scrollTop = document.body.scrollTop = scrollTop - headerHeight;
}
}
document.body.addEventListener( 'focus', handleFocus, true );
@ffoodd
Copy link

ffoodd commented Jun 19, 2018

Hi there!

In my current project I needed to tweak this a bit: offsetTop isn't reliable enough because of (too) many absolute positions.
So to get focustTop I used e.target.getBoundingClientRect().top + document.documentElement.scrollTop and it's fine, now.

It also decreases condition complexity: focusTop <= (scrollTop + headerHeight) seems sufficient.

I didn't test this cross-browser for now, but I'll get back if I come across any trouble :)

@enwin
Copy link
Author

enwin commented Jun 20, 2018

Thanks for the feedback!

I simplified the gist because getBoundingClientRect() returns positions based on the viewport, not the document. We can directly compare the .top with the height of the sticky bar. We still need scrollTop but only to place the scroll at the desired position.

As for the cross-browser compatibility. Webkit/Blink based browser will never trigger the repositioning since their focus algorithm seems to always put the focused element at the center of the screen (when there's enough space around).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment