Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaScript scrollIntoView smooth scroll and offset

I have this code for my website:

function clickMe() {
  var element = document.getElementById('about');
  element.scrollIntoView({
    block: 'start',
    behavior: 'smooth',
  });
}

This works pretty nice but I have a fixed header so when the code scrolls to the element the header is in the way.

Is there a way to have an offset and make it scroll smoothly?

like image 542
Tim Wijma Avatar asked Apr 13 '18 15:04

Tim Wijma


People also ask

Can you offset scrollIntoView?

To use JavaScript scrollIntoView to do smooth scroll with offset, we can call scrollTo with an object that has the top and behavior properties. const element = document.

How do I make scrollIntoView smooth?

Native method: scrollIntoView The usage is simple, just like this: function scroll(e) {e. scrollIntoView({behavior: "smooth", block: "start"});}scroll(document. getElementById(YOUR_DIV_ID));

Can I use scrollIntoView smooth?

scrollIntoView() method scrolls the current element into the visible area of the browser window. Parameters can be provided to set the position inside the visible area as well as whether scrolling should be instant or smooth.


3 Answers

Is there a way to have an offset and make it scroll smoothly?

#Yes, but not with scrollIntoView()

The scrollIntoViewOptions of Element.scrollIntoView() do not allow you to use an offset. It is solely useful when you want to scroll to the exact position of the element.

You can however use Window.scrollTo() with options to both scroll to an offset position and to do so smoothly.

If you have a header with a height of 30px for example you might do the following:

function scrollToTargetAdjusted(){
    var element = document.getElementById('targetElement');
    var headerOffset = 45;
    var elementPosition = element.getBoundingClientRect().top;
    var offsetPosition = elementPosition + window.pageYOffset - headerOffset;
  
    window.scrollTo({
         top: offsetPosition,
         behavior: "smooth"
    });
}

This will smoothly scroll to your element just so that it is not blocked from view by your header.

Note: You substract the offset because you want to stop before you scroll your header over your element.

#See it in action

You can compare both options in the snippet below.

<script type="text/javascript">
  function scrollToTarget() {

    var element = document.getElementById('targetElement');
    element.scrollIntoView({
      block: "start",
      behavior: "smooth",
    });
  }

  function scrollToTargetAdjusted() {
        var element = document.getElementById('targetElement');
      var headerOffset = 45;
        var elementPosition = element.getBoundingClientRect().top;
      var offsetPosition = elementPosition + window.pageYOffset - headerOffset;
      
      window.scrollTo({
          top: offsetPosition,
          behavior: "smooth"
      });   
  }

  function backToTop() {
    window.scrollTo(0, 0);
  }
</script>

<div id="header" style="height:30px; width:100%; position:fixed; background-color:lightblue; text-align:center;"> <b>Fixed Header</b></div>

<div id="mainContent" style="padding:30px 0px;">

  <button type="button" onclick="scrollToTarget();">element.scrollIntoView() smooth, header blocks view</button>
  <button type="button" onclick="scrollToTargetAdjusted();">window.scrollTo() smooth, with offset</button>

  <div style="height:1000px;"></div>
  <div id="targetElement" style="background-color:red;">Target</div>
  <br/>
  <button type="button" onclick="backToTop();">Back to top</button>
  <div style="height:1000px;"></div>
</div>

Edit

window.pageYOffset have being added, to fix the problem related to @coreyward comments

like image 103
Søren D. Ptæus Avatar answered Oct 16 '22 15:10

Søren D. Ptæus


Søren D. Ptæus's answer got me on the right track but I had issues with getBoundingClientRect() when not at the top of the window.

My solution adds a bit more to his to get getBoundingClientRect() working a bit more consistently with more versatility. I used the approach outlined here and implemented it to get this working as intended.

const element = document.getElementById('targetElement');
const offset = 45;
const bodyRect = document.body.getBoundingClientRect().top;
const elementRect = element.getBoundingClientRect().top;
const elementPosition = elementRect - bodyRect;
const offsetPosition = elementPosition - offset;

window.scrollTo({
  top: offsetPosition,
  behavior: 'smooth'
});

Codepen Example

Remember to include the polyfill when implementing this!

like image 35
ekfuhrmann Avatar answered Oct 16 '22 14:10

ekfuhrmann


Simple but elegant solution if the element has a small height (shorter than the viewport):

element.scrollIntoView({ behavior: 'auto' /*or smooth*/, block: 'center' });

The block: center will scroll the element so the center of the element is at the vertical center of the viewport, so the top header will not cover it.

EDIT 8.5.22: behavior: instant was used in the past, but removed from browsers.

like image 56
Moshe L Avatar answered Oct 16 '22 16:10

Moshe L