Here is the function that I wrote based on the @ekfuhrmann's answer.
It takes the element that needs to be scrolled to as the first parameter and other options in the form of the object as the second parameter, similar to how the window.scrollTo()
function works.
function scrollToTarget(element, options) {
if (options.headerHeight === undefined) {
options.headerHeight = 0;
}
var elementRect = element.getBoundingClientRect();
// If an element has 0 height, then it is hidden, do not scroll
if (elementRect.height == 0) {
return;
}
var offset = elementRect.top - options.headerHeight;
if (options.block == 'center') {
// If an element's height is smaller, than the available screen height (without the height of the header), then add the half of the available space
// to scroll to the center of the screen
var availableSpace = window.innerHeight - options.headerHeight;
if (elementRect.height < availableSpace) {
offset -= (availableSpace - elementRect.height) / 2;
}
}
var optionsToPass = {
top: offset
};
if (options.behavior !== undefined) {
optionsToPass.behavior = options.behavior
}
window.scrollBy(optionsToPass);
}
The main difference is that it uses window.scrollBy()
function instead of window.scrollTo()
, so that we don't need to call .getBoundingClientRect()
on body
.
The options
parameter can contain a headerHeight
field - it can contain the height of the fixed element on the screen, that needs to be ignored when scrolling to the element.
This function can also have a block
option, that for now can only accept a single "center"
value. When set, the element which is scrolled to will appear in the center of the screen excluding the fixed element height. By default, the scroll will be applied to the element's top.
Usage example
Here we have two overlapping elements with fixed position. Let's imagine the largest of them is not visible on some viewport widths, so we need to dynamically get the available viewport height minus the height of fixed element.
The following example demonstrates, that the element will appear in the center of the available viewport height if the block
option is set to "center"
, similar to how the Element.scrollIntoView()
function works.
function scrollToTarget(element, options) {
if (options.headerHeight === undefined) {
options.headerHeight = 0;
}
var elementRect = element.getBoundingClientRect();
if (elementRect.height == 0) {
return;
}
var offset = elementRect.top - options.headerHeight;
if (options.block == 'center') {
var availableSpace = window.innerHeight - options.headerHeight;
if (elementRect.height < availableSpace) {
offset -= (availableSpace - elementRect.height) / 2;
}
}
var optionsToPass = {
top: offset
};
if (options.behavior !== undefined) {
optionsToPass.behavior = options.behavior
}
window.scrollBy(optionsToPass);
}
var headerElements = [
document.querySelector('.header__wrap'),
document.getElementById('wpadminbar')
];
var maxHeaderHeight = headerElements.reduce(function (max, item) {
return item ? Math.max(max, item.offsetHeight) : max;
}, 0);
document.getElementById('click-me').addEventListener('click', function() {
scrollToTarget(document.querySelector('.scroll-element'), {
headerHeight: maxHeaderHeight,
block: 'center',
behavior: 'smooth'
});
});
body {
margin: 0;
height: 1000px;
}
#wpadminbar, .header__wrap {
position: fixed;
top: 0;
left: 0;
right: 0;
}
#wpadminbar {
height: 32px;
background-color: #1d2327;
z-index: 2;
opacity: 0.8;
}
.header__wrap {
margin: 0 15px;
height: 74px;
background-color: #436c50;
z-index: 1;
}
.scroll-element {
margin-top: 500px;
padding: 1em;
text-align: center;
background-color: #d7d7d7;
}
#click-me {
margin: 100px auto 0;
padding: 0.5em 1em;
display: block;
}
<div id="wpadminbar"></div>
<div class="header__wrap"></div>
<button id="click-me">Click me!</button>
<!-- Some deeply nested HTML element -->
<div class="scroll-element">
You scrolled to me and now I am in the visual center of the screen. Nice!
</div>
padding-top
, which acts as the offset. – Aseity