scrollIntoView Scrolls just too far

JavascriptDomScroll

Javascript Problem Overview


I have a page where a scroll bar containing table rows with divs in them is dynamically generated from the database. Each table row acts like a link, sort of like you'd see on a YouTube playlist next to the video player.

When a user visits the page, the option they are on is supposed to go to the top of the scrolling div. This functionality is working. The issue is that it goes just a tad too far. Like the option they are on is about 10px too high. So, the page is visited, the url is used to identify which option was selected and then scrolls that option to the top of the scrolling div. Note: This is not the scroll bar for the window, it is a div with a scrollbar.

I am using this code to make it move the selected option to the top of the div:

var pathArray = window.location.pathname.split( '/' );
						
var el = document.getElementById(pathArray[5]);
						
el.scrollIntoView(true);

It moves it to the top of the div but about 10 pixels too far up. Anyone know how to fix that?

Javascript Solutions


Solution 1 - Javascript

Smooth scroll to the proper position

Get correct y coordinate and use window.scrollTo({top: y, behavior: 'smooth'})

const id = 'profilePhoto';
const yOffset = -10; 
const element = document.getElementById(id);
const y = element.getBoundingClientRect().top + window.pageYOffset + yOffset;

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

Solution 2 - Javascript

Position Anchor By Absolute Method

Another way to do this is to position your anchors exactly where you want on the page rather than relying on scrolling by offset. I find it allows better control for each element (eg. if you want a different offset for certain elements), and may also be more resistant to browser API changes/differences.

<div id="title-element" style="position: relative;">
  <div id="anchor-name" style="position: absolute; top: -100px; left: 0"></div>
</div>

Now the offset is specified as -100px relative to the element. Create a function to create this anchor for code reuse, or if you are using a modern JS framework such as React do this by creating a component that renders your anchor, and pass in the anchor name and alignment for each element, which may or may not be the same.

Then just use :

const element = document.getElementById('anchor-name')
element.scrollIntoView({ behavior: 'smooth', block: 'start' });

For smooth scrolling with an offset of 100px.

Solution 3 - Javascript

You can do it in two steps :

el.scrollIntoView(true);
window.scrollBy(0, -10); // Adjust scrolling with a negative value here

Solution 4 - Javascript

If it's about 10px, then I guess you could simply manually adjust the containing div's scroll offset like that:

el.scrollIntoView(true);
document.getElementById("containingDiv").scrollTop -= 10;

Solution 5 - Javascript

I solved this problem by using,

element.scrollIntoView({ behavior: 'smooth', block: 'center' });

This makes the element appear in the center after scrolling, so I don't have to calculate yOffset.

Hope it helps...

Solution 6 - Javascript

This works for me in Chrome (With smooth scrolling and no timing hacks)

It just moves the element, initiates the scroll, then moves it back.

There is no visible "popping" if the element is already on the screen.

pos = targetEle.style.position;
top = targetEle.style.top;
targetEle.style.position = 'relative';
targetEle.style.top = '-20px';
targetEle.scrollIntoView({behavior: 'smooth', block: 'start'});
targetEle.style.top = top;
targetEle.style.position = pos;

Solution 7 - Javascript

CSS scroll-margin and scroll-padding

You might want to have a look at new CSS properties scroll-padding and scroll-margin. You can use scroll-padding for the scrolling container (html in this case), and scroll-margin for the element within the container.

For your example, you would want to add scroll-margin-top for the element that you want to scroll into view, like this:

.example {
  scroll-margin-top: 10px;
}

This affects scrollIntoView code, like this code:

const el = document.querySelector(".example");
el.scrollIntoView({block: "start", behavior: "smooth"});

This will cause the viewport to scroll to align the top border of the viewport with the top border of the element, but with 10px of additional space. In other words, these properties of the element are taken into account:

  • padding-top
  • border-top
  • scroll-margin-top
  • (and not margin-top)

In addition, if the html element has scroll-padding-top set, then that is taken into account too.

Solution 8 - Javascript

Fix it in 20 seconds:

This solution belongs to @Arseniy-II, I have just simplified it into a function.

function _scrollTo(selector, yOffset = 0){
  const el = document.querySelector(selector);
  const y = el.getBoundingClientRect().top + window.pageYOffset + yOffset;

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

Usage (you can open up the console right here in StackOverflow and test it out):

_scrollTo('#question-header', 0);

I'm currently using this in production and it is working just fine.

Solution 9 - Javascript

Building on an earlier answer, I am doing this in an Angular5 project.

Started with:

// el.scrollIntoView(true);
el.scrollIntoView({
   behavior: 'smooth',
   block: 'start'
});
window.scrollBy(0, -10); 

But this gave some problems and needed to setTimeout for the scrollBy() like this:

//window.scrollBy(0,-10);
setTimeout(() => {
  window.scrollBy(0,-10)
  }, 500);

And it works perfectly in MSIE11 and Chrome 68+. I have not tested in FF. 500ms was the shortest delay I would venture. Going lower sometimes failed as the smooth scroll had not yet completed. Adjust as required for your own project.

+1 to Fred727 for this simple but effective solution.

Solution 10 - Javascript

Assuming you want to scroll to the divs that are all at the same level in DOM and have class name "scroll-with-offset", then this CSS will solve the issue:

.scroll-with-offset {    
  padding-top: 100px;
  margin-bottom: -100px;
}

The offset from the top of the page is 100px. It will only work as intended with block: 'start':

element.scrollIntoView({ behavior: 'smooth', block: 'start' });

What's happening is that the divs' top point is at the normal location but their inner contents start 100px below the normal location. That's what padding-top:100px is for. margin-bottom: -100px is to offset the below div's extra margin. To make the solution complete also add this CSS to offset the margins/paddings for the top-most and bottom-most divs:

.top-div {
  padding-top: 0;
}
.bottom-div {
  margin-bottom: 0;
}

Solution 11 - Javascript

Another solution is to use "offsetTop", like this:

var elementPosition = document.getElementById('id').offsetTop;

window.scrollTo({
  top: elementPosition - 10, //add your necessary value
  behavior: "smooth"  //Smooth transition to roll
});

Solution 12 - Javascript

You can also use the element.scrollIntoView() options

el.scrollIntoView(
  { 
    behavior: 'smooth', 
    block: 'start' 
  },
);

which most browsers support

Solution 13 - Javascript

I've got this and it works brilliantly for me:

// add a smooth scroll to element
scroll(el) {
el.scrollIntoView({
  behavior: 'smooth',
  block: 'start'});

setTimeout(() => {
window.scrollBy(0, -40);
}, 500);}

Hope it helps.

Solution 14 - Javascript

So, perhaps this is a bit clunky but so far so good. Im working in angular 9.

file .ts

scroll(el: HTMLElement) {
  el.scrollIntoView({ block: 'start',  behavior: 'smooth' });   
}

file .html

<button (click)="scroll(target)"></button>
<div  #target style="margin-top:-50px;padding-top: 50px;" ></div>

I adjust the offset with margin and padding top.

Saludos!

Solution 15 - Javascript

Solution if you are using Ionic Capacitor, Angular Material, and need to support iOS 11.

                document.activeElement.parentElement.parentElement.scrollIntoView({block: 'center', behavior: 'smooth'}); 

The key is to scroll to the parent of the parent which is the wrapper around the input. This wrapper includes the label for the input which is now no longer cut off.

If you only need to support iOS 14 the "block" center param actually works, so this is sufficient:

                document.activeElement.scrollIntoView({block: 'center', behavior: 'smooth'}); 

Solution 16 - Javascript

Found a workaround solution. Say that you want to scroll to an div, Element here for example, and you want to have a spacing of 20px above it. Set the ref to a created div above it:

<div ref={yourRef} style={{position: 'relative', bottom: 20}}/> <Element />

Doing so will create this spacing that you want.

If you have a header, create an empty div as well behind the header and assign to it a height equal to the height of the header and reference it.

Solution 17 - Javascript

My main idea is creating a tempDiv above the view which we want to scroll to. It work well without lagging in my project.

scrollToView = (element, offset) => {
    var rect = element.getBoundingClientRect();
    var targetY = rect.y + window.scrollY - offset;

    var tempDiv;
    tempDiv = document.getElementById("tempDiv");
    if (tempDiv) {
        tempDiv.style.top = targetY + "px";
    } else {
        tempDiv = document.createElement('div');
        tempDiv.id = "tempDiv";
        tempDiv.style.background = "#F00";
        tempDiv.style.width = "10px";
        tempDiv.style.height = "10px";
        tempDiv.style.position = "absolute";
        tempDiv.style.top = targetY + "px";
        document.body.appendChild(tempDiv);
    }

    tempDiv.scrollIntoView({ behavior: 'smooth', block: 'start' });
}

Example using

onContactUsClick = () => {
    this.scrollToView(document.getElementById("contact-us"), 48);
}

Hope it help

Solution 18 - Javascript

Based on the answer of Arseniy-II: I had the Use-Case where the scrolling entity was not window itself but a inner Template (in this case a div). In this scenario we need to set an ID for the scrolling container and get it via getElementById to use its scrolling function:

<div class="scroll-container" id="app-content">
  ...
</div>
const yOffsetForScroll = -100
const y = document.getElementById(this.idToScroll).getBoundingClientRect().top;
const main = document.getElementById('app-content');
main.scrollTo({
    top: y + main.scrollTop + yOffsetForScroll,
    behavior: 'smooth'
  });

Leaving it here in case someone faces a similar situation!

Solution 19 - Javascript

Here's my 2 cents.

I've also had the issue of the scrollIntoView scrolling a bit past the element, so I created a script (native javascript) that prepends an element to the destination, positioned it a bit to the top with css and scrolled to that one. After scrolling, I remove the created elements again.

HTML:

//anchor tag that appears multiple times on the page
<a href="#" class="anchors__link js-anchor" data-target="schedule">
	<div class="anchors__text">
		Scroll to the schedule
	</div>
</a>

//The node we want to scroll to, somewhere on the page
<div id="schedule">
    //html
</div>

Javascript file:

(() => {
    'use strict';

    const anchors = document.querySelectorAll('.js-anchor');

    //if there are no anchors found, don't run the script
    if (!anchors || anchors.length <= 0) return;

    anchors.forEach(anchor => {
        //get the target from the data attribute
        const target = anchor.dataset.target;

        //search for the destination element to scroll to
        const destination = document.querySelector(`#${target}`);
        //if the destination element does not exist, don't run the rest of the code
        if (!destination) return;

        anchor.addEventListener('click', (e) => {
            e.preventDefault();
            //create a new element and add the `anchors__generated` class to it
            const generatedAnchor = document.createElement('div');
            generatedAnchor.classList.add('anchors__generated');

            //get the first child of the destination element, insert the generated element before it. (so the scrollIntoView function scrolls to the top of the element instead of the bottom)
            const firstChild = destination.firstChild;
            destination.insertBefore(generatedAnchor, firstChild);

            //finally fire the scrollIntoView function and make it animate "smoothly"
            generatedAnchor.scrollIntoView({
                behavior: "smooth",
                block: "start",
                inline: "start"
            });

            //remove the generated element after 1ms. We need the timeout so the scrollIntoView function has something to scroll to.
            setTimeout(() => {
                destination.removeChild(generatedAnchor);
            }, 1);
        })
    })
})();

CSS:

.anchors__generated {
    position: relative;
    top: -100px;
}

Hope this helps anyone!

Solution 20 - Javascript

I add this css tips for those who not resolved this issue with solutions above :

#myDiv::before {
  display: block;
  content: " ";
  margin-top: -90px; // adjust this with your header height
  height: 90px; // adjust this with your header height
  visibility: hidden;
}

Solution 21 - Javascript

UPD: I've created an npm package that works better than the following solution and easier to use.

My smoothScroll function

I've taken the wonderful solution of Steve Banton and wrote a function that makes it more convenient to use. It'd be easier just to use window.scroll() or even window.scrollBy(), as I've tried before, but these two have some problems:

  • Everything becomes junky after using them with a smooth behavior on.
  • You can't prevent them anyhow and have to wait till the and of the scroll. So I hope my function will be useful for you. Also, there is a lightweight polyfill that makes it work in Safari and even IE.

Here is the code

Just copy it and mess up with it how ever you want.

import smoothscroll from 'smoothscroll-polyfill';

smoothscroll.polyfill();

const prepareSmoothScroll = linkEl => {
  const EXTRA_OFFSET = 0;

  const destinationEl = document.getElementById(linkEl.dataset.smoothScrollTo);
  const blockOption = linkEl.dataset.smoothScrollBlock || 'start';

  if ((blockOption === 'start' || blockOption === 'end') && EXTRA_OFFSET) {
    const anchorEl = document.createElement('div');

    destinationEl.setAttribute('style', 'position: relative;');
    anchorEl.setAttribute('style', `position: absolute; top: -${EXTRA_OFFSET}px; left: 0;`);

    destinationEl.appendChild(anchorEl);

    linkEl.addEventListener('click', () => {
      anchorEl.scrollIntoView({
        block: blockOption,
        behavior: 'smooth',
      });
    });
  }

  if (blockOption === 'center' || !EXTRA_OFFSET) {
    linkEl.addEventListener('click', () => {
      destinationEl.scrollIntoView({
        block: blockOption,
        behavior: 'smooth',
      });
    });
  }
};

export const activateSmoothScroll = () => {
  const linkEls = [...document.querySelectorAll('[data-smooth-scroll-to]')];

  linkEls.forEach(linkEl => prepareSmoothScroll(linkEl));
};

To make a link element just add the following data attribute:

data-smooth-scroll-to="element-id"

Also you can set another attribute as an addtion

data-smooth-scroll-block="center"

It represents the block option of the scrollIntoView() function. By default, it's start. Read more on MDN.

Finally

Adjust the smoothScroll function to your needs.

For example, if you have some fixed header (or I call it with the word masthead) you can do something like this:

const mastheadEl = document.querySelector(someMastheadSelector);

// and add it's height to the EXTRA_OFFSET variable

const EXTRA_OFFSET = mastheadEl.offsetHeight - 3;

If you don't have such a case, then just delete it, why not :-D.

Solution 22 - Javascript

I tried using some of the answers from above, but the scrolling would not work. After some digging around I noticed that the scroll positions were in fact very odd (even negative values) and it was caused by how Angular was rendering the pages in the router-outlet.

My solution was to compute the scroll position of the element starting from the top of the page. I placed an element with id start-of-page at the beginning of the template and I obtained the scroll amount by subtracting the target fragment position.

 ngAfterViewInit(): void {
    this.route.fragment.subscribe(fragment => {
      try {
        // @ts-ignore
        // document.querySelector('#hero').scrollIntoView({behavior:smooth, block: "start", inline: "start"});
        // @ts-ignore
        // document.querySelector('#' + fragment).scrollIntoView({behavior:smooth, block: "start", inline: "start"});
        this._scrollTo('#' + fragment,0);
      } catch (e) { };
    });
  }

private _scrollTo(selector: any, yOffset = 0){
    const base = document.querySelector('#start-of-page');
    const el = document.querySelector(selector);
    // @ts-ignore
    const y = el.getBoundingClientRect().top - base.getBoundingClientRect().top; 
    window.scrollTo({top: y, behavior: 'smooth'});
  }

Solution 23 - Javascript

One solution is to create an invisible temporary element with an offset relative to the target, scroll to it, and then delete it. It's perhaps not ideal, but other solutions weren't working for me in the context I was working in. For example:

const createOffsetElement = (element) => {
    const offsetElement = document.createElement('div');
    offsetElement.style = `
        position: relative;
        top: -10em;
        height: 1px;
        width: 1px;
        visibility: hidden;
    `;
    const inputWrapper = element.closest('.ancestor-element-class');
    inputWrapper.appendChild(offsetElement);
    return offsetElement;
};

const scrollTo = (element) => {
    const offsetElement = createOffsetElement(element);
    offsetElement.scrollIntoView({
        behavior: 'smooth',
    });
    offsetElement.remove();
};

Solution 24 - Javascript

The simplest way to do this,

html, body {
    scroll-behavior: smooth;
}

    html [id], body [id] {
        scroll-margin: 50px !important;
    }

with your provided code

var pathArray = window.location.pathname.split( '/' );
var el = document.getElementById(pathArray[5]);
el.style.scrollMargin = 50px !important;
el.scrollIntoView(true);

Solution 25 - Javascript

Simple solution for scrolling specific element down

const element = document.getElementById("element-with-scroll");
element.scrollTop = element.scrollHeight - 10;

Solution 26 - Javascript

For an element in a table row, use JQuery to get the row above the one you want, and simply scroll to that row instead.

Suppose I have multiple rows in a table, some of which should be reviewed by and admin. Each row requiring review has both and up and a down arrow to take you to the previous or next item for review.

Here's a complete example that should just run if you make a new HTML document in notepad and save it. There's extra code to detect the top and bottom of our items for review so we don't throw any errors.

<html>
<head>
    <title>Scrolling Into View</title>
    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
    <style>
        div.scroll { height: 6em; width: 20em; overflow: auto; }
        thead th   { position: sticky; top: -1px; background: #fff; }
        .up, .down { cursor: pointer; }
        .up:hover, .down:hover { color: blue; text-decoration:underline; }
    </style>
</head>
<body>
<div class='scroll'>
<table border='1'>
    <thead>
        <tr>
            <th>Review</th>
            <th>Data</th>
        </tr>
    </thead>
    <tbody>
        <tr id='row_1'>
            <th></th>
            <td>Row 1 (OK)</td>
        </tr>
        <tr id='row_2'>
            <th></th>
            <td>Row 2 (OK)</td>
        </tr>
        <tr id='row_3'>
            <th id='jump_1'><span class='up'>UP</span> <span class='down'>DN</span></th>
            <td>Row 3 (REVIEW)</td>
        </tr>
        <tr id='row_4'>
            <th></th>
            <td>Row 4 (OK)</td>
        </tr>
        <tr id='row_5'>
            <th id='jump_2'><span class='up'>UP</span> <span class='down'>DN</span></th>
            <td>Row 5 (REVIEW)</td>
        </tr>
        <tr id='row_6'>
            <th></th>
            <td>Row 6 (OK)</td>
        </tr>
        <tr id='row_7'>
            <th></th>
            <td>Row 7 (OK)</td>
        </tr>
        <tr id='row_8'>
            <th id='jump_3'><span class='up'>UP</span> <span class='down'>DN</span></th>
            <td>Row 8 (REVIEW)</td>
        </tr>
        <tr id='row_9'>
            <th></th>
            <td>Row 9 (OK)</td>
        </tr>
        <tr id='row_10'>
            <th></th>
            <td>Row 10 (OK)</td>
        </tr>
    </tbody>
</table>
</div>
<script>
$(document).ready( function() {
    $('.up').on('click', function() {
        var id = parseInt($(this).parent().attr('id').split('_')[1]);
        if (id>1) {
            var row_id = $('#jump_' + (id - 1)).parent().attr('id').split('_')[1];
            document.getElementById('row_' + (row_id-1)).scrollIntoView({behavior: 'smooth', block: 'start'});
        } else {
            alert('At first');
        }
    });

    $('.down').on('click', function() {
        var id = parseInt($(this).parent().attr('id').split('_')[1]);
        if ($('#jump_' + (id + 1)).length) {
            var row_id = $('#jump_' + (id + 1)).parent().attr('id').split('_')[1];
            document.getElementById('row_' + (row_id-1)).scrollIntoView({behavior: 'smooth', block: 'start'});
        } else {
            alert('At last');
        }
    });
});
</script>
</body>
</html>

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMatthew WilsonView Question on Stackoverflow
Solution 1 - JavascriptArseniy-IIView Answer on Stackoverflow
Solution 2 - JavascriptSteve BantonView Answer on Stackoverflow
Solution 3 - Javascriptfred727View Answer on Stackoverflow
Solution 4 - JavascriptLucas TrzesniewskiView Answer on Stackoverflow
Solution 5 - JavascriptImtiaz Shakil SiddiqueView Answer on Stackoverflow
Solution 6 - JavascriptRyan HowView Answer on Stackoverflow
Solution 7 - JavascriptFlimmView Answer on Stackoverflow
Solution 8 - JavascriptDiego FortesView Answer on Stackoverflow
Solution 9 - JavascriptPrimaryWView Answer on Stackoverflow
Solution 10 - JavascriptGeorgy PetukhovView Answer on Stackoverflow
Solution 11 - JavascriptKaio DutraView Answer on Stackoverflow
Solution 12 - JavascriptNicholas MurrayView Answer on Stackoverflow
Solution 13 - JavascriptFernando BrandaoView Answer on Stackoverflow
Solution 14 - JavascriptIgnacio BastiView Answer on Stackoverflow
Solution 15 - Javascriptjfbloom22View Answer on Stackoverflow
Solution 16 - JavascriptIbrahim Al MasriView Answer on Stackoverflow
Solution 17 - JavascriptLinhView Answer on Stackoverflow
Solution 18 - JavascriptkounexView Answer on Stackoverflow
Solution 19 - JavascriptrubeusView Answer on Stackoverflow
Solution 20 - JavascriptJérémy MARQUERView Answer on Stackoverflow
Solution 21 - JavascriptDmitry ZakharovView Answer on Stackoverflow
Solution 22 - JavascriptRadu IonescuView Answer on Stackoverflow
Solution 23 - JavascriptAnomalyView Answer on Stackoverflow
Solution 24 - JavascriptMalik ZahidView Answer on Stackoverflow
Solution 25 - JavascriptHuriView Answer on Stackoverflow
Solution 26 - JavascriptMartin FrancisView Answer on Stackoverflow