221

I am using ScrollIntoView() to scroll the highlighted item in a list into view.
When I scroll downwards ScrollIntoView(false) works perfectly.

But when I scroll upwards, ScrollIntoView(true) is causing the whole page to move a little which I think is intended.

Is there a way to avoid the whole page move when using ScrollIntoView(true)?

Here is the structure of my page

#listOfDivs {
   position:fixed;
   top:100px;
   width: 300px;
   height: 300px;
   overflow-y: scroll;
}

<div id="container">
     <div id="content"> 
          <div id="listOfDivs"> 
               <div id="item1"> </div>
               <div id="item2"> </div>
               <div id="itemn"> </div>
          </div>
     </div>
</div>

listOfDivs is coming from ajax call. Using mobile safari.

Laaouatni Anas
  • 4,199
  • 2
  • 7
  • 26
imhere
  • 4,405
  • 7
  • 26
  • 25

15 Answers15

313

Fixed it with:

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

see: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView

jfrohn
  • 3,131
  • 1
  • 6
  • 8
  • 12
    What does "nearest" mean? EDIT: Found an answer here: https://stackoverflow.com/a/48635751/628418 – Sámal Rasmussen Jul 10 '19 at 13:52
  • 3
    This resolved my issue. I've been seeing this happen in our web app for some time, but it's been hard to lock down the repro for it. After adding a new feature it occurred every time and I was able to trace it to this call, which was missing the `block : 'nearest'` param. – roskelld Jul 18 '19 at 20:47
  • If only they defined what the block values mean.... start and nearest... its all trial and error. – Bae Sep 11 '19 at 07:40
  • 2
    Works with Chrome, but not with Edge for me. – Michael Oct 08 '19 at 15:18
  • 10
    "block: 'nearest'" works, but "block: 'center'" (which is a bit nicer) still scrolls the viewport. It would be nice to know whether this is a bug or a feature... – GrumpyGary Jan 17 '20 at 18:46
  • This didn't work for me in either firefox, nor chrome. Using React and I tried: `elem.scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'start' })` – krankit Jan 28 '20 at 10:50
  • @Bae I think it's intuitive what those values mean for `block`, but yeah I agree they should specify in documentation. – David Callanan Feb 15 '20 at 10:25
  • This worked for custom select drop-down where the page kept centering the menu. "nearest" was the solution. – SoEzPz Jul 30 '20 at 14:38
  • 1
    Notice that Safari still does not support this, and since this could cause big page-viewability issues we have to go with Briliand's answer. – Nate Levin Aug 21 '20 at 01:36
  • This answer saver my life along with this library https://www.npmjs.com/package/scroll-into-view-if-needed – Nishant Patel Nov 07 '20 at 16:45
  • 1
    I was able to resolve this with `{ block: 'nearest', inline: 'center' }` by also applying `overflow: clip` to the parent element that was over-scrolling (in my case a horizontal over-scroll) – som Mar 19 '21 at 05:50
  • Still correct as of May 2022. I had been using `block: "start"` on my elements (a horizontal UL) but upon scrolling above this block, I would get pulled back down the page to the `
      `, switching to `block: "nearest"` removes that unwanted feature.
    – Andrew West May 04 '22 at 13:31
  • nothing helped me, but i fixed whole page moving, by reducing it's height, so it can fit without overflowing, therefore then it is not affected by scrolls. – rlf89 Jun 20 '22 at 21:36
  • 2
    @som I had a similar issue where my whole page was offset after using `scrollIntoView()` and the thing that prevented this for me in the end was `overflow: clip;` on the main element. Many thanks! – Alexander Rutz Dec 08 '22 at 20:14
178

You could use scrollTop instead of scrollIntoView():

var target = document.getElementById("target");
target.parentNode.scrollTop = target.offsetTop;

jsFiddle: http://jsfiddle.net/LEqjm/

If there's more than one scrollable element that you want to scroll, you'll need to change the scrollTop of each one individually, based on the offsetTops of the intervening elements. This should give you the fine-grained control to avoid the problem you're having.

EDIT: offsetTop isn't necessarily relative to the parent element - it's relative to the first positioned ancestor. If the parent element isn't positioned (relative, absolute or fixed), you may need to change the second line to:

target.parentNode.scrollTop = target.offsetTop - target.parentNode.offsetTop;
Brilliand
  • 13,404
  • 6
  • 46
  • 58
  • 7
    FYI, this only works when the parent is at the top of the page. You should probably make it `target.parentNode.scrollTop = target.offsetTop - target.parentNode.offsetTop` – Phil Mar 20 '13 at 23:56
  • 2
    offsetTop is relative to the nearest positioned parent, so if the parent node has `position: relative`, then you shouldn't subtract its offset. But you're right for most cases. – Brilliand Mar 22 '13 at 20:01
  • 4
    Here is a js fiddle: http://jsfiddle.net/LEqjm/258/ that shows also the ScrollIntoView improper behavior. – Rafi Sep 01 '16 at 12:37
  • 1
    @Brilliand thank you for idea about `position: relative` on prent div: it really fix the problem – Alex Zhukovskiy Dec 07 '16 at 15:49
  • It is working on chrome but it scrolls little bit more on IE, which hides the text content from the top. Any solution @Phil, Thanks in advance – Soniya Jun 08 '18 at 04:13
  • @Soniya I'm not seeing that problem with my jsFiddle. You'll have to study your specific situation to figure out what's affecting it. – Brilliand Jun 08 '18 at 22:37
  • While this has stopped the page moving up and down for me, I still have problems on scroll up. I have a dropdown where I select items up and down keys, and need to scroll to keep the selected item visible. But when I scroll up, it just doesn't scroll sufficiently and the selected element ends up "escaping" to the top. – Shaggydog Sep 30 '19 at 16:52
  • @Shaggydog do you by any chance have an element with `position: fixed` covering up the top of your page? – Brilliand Sep 30 '19 at 21:33
  • target.parentElement would be better here – Anthony Johnston May 11 '21 at 13:37
  • @AnthonyJohnston `target.parentElement` would be exactly the same here. It only has the most trivial of differences from `target.parentNode`, and imo those differences make it worse if anything. – Brilliand May 11 '21 at 18:21
  • @Brilliand the reason I say this is because a parentNode doesn't have a scrollTop property according to this https://developer.mozilla.org/en-US/docs/Web/API/Node/parentNode, it works because its not typed and usually is a HTMLElement. Also of note target doesn't have the offsetTop property either.. I guess it doesn't really matter in the main, unless you are using Typescript (which I recommend) – Anthony Johnston May 13 '21 at 08:17
  • This should not be the accepted answer, check out the answer of jfrohn instead! – parentNode Jan 26 '22 at 14:40
  • @parentNode Oh wow, the browser makers added a new feature specifically to solve this question. ...It still doesn't work in all browsers, though. – Brilliand Jan 27 '22 at 22:06
23
var el = document.querySelector("yourElement");
window.scroll({top: el.offsetTop, behavior: 'smooth'});
dipole_moment
  • 5,266
  • 4
  • 39
  • 55
20

I had this problem too, and spent many hours trying to deal with it. I hope my resolution may still help some people.

My fix ended up being:

  • For Chrome: changing .scrollIntoView() to .scrollIntoView({block: 'nearest'}) (thanks to @jfrohn).
  • For Firefox: apply overflow: -moz-hidden-unscrollable; on the container element that shifts.
  • Not tested in other browsers.
paddotk
  • 1,359
  • 17
  • 31
15

in my context, he would push the sticky toolbar off the screen, or enter next to a fab button with absolute.

using the nearest solved.

const element = this.element.nativeElement;
const table = element.querySelector('.table-container');
table.scrollIntoView({
  behavior: 'smooth', block: 'nearest'
});
Gui Seek
  • 421
  • 4
  • 5
13

Play around with scrollIntoViewIfNeeded() ... make sure it's supported by the browser.

Ɖiamond ǤeezeƦ
  • 3,223
  • 3
  • 28
  • 40
Jesco
  • 131
  • 1
  • 2
  • 1
    Didn't know [that](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoViewIfNeeded) was a thing. There's [a polyfill](https://gist.github.com/hsablonniere/2581101) – mpen Mar 11 '16 at 19:47
  • In document, its mentioned there that it is non standard but it's working fine. should we use it? – Shyam Kumar Mar 07 '19 at 13:05
10

I've added a way to display the imporper behavior of the ScrollIntoView - http://jsfiddle.net/LEqjm/258/ [it should be a comment but I don't have enough reputation]

$("ul").click(function() {
    var target = document.getElementById("target");
    if ($('#scrollTop').attr('checked')) {
        target.parentNode.scrollTop = target.offsetTop;    
    } else {
        target.scrollIntoView(!0);
    }
});
Krisztián Balla
  • 19,223
  • 13
  • 68
  • 84
Michal Tsadok
  • 143
  • 1
  • 4
8

jQuery plugin scrollintoview() increases usability

Instead of default DOM implementation you can use a plugin that animates movement and doesn't have any unwanted effects. Here's the simplest way of using it with defaults:

$("yourTargetLiSelector").scrollintoview();

Anyway head over to this blog post where you can read all the details and will eventually get you to GitHub source codeof the plugin.

This plugin automatically searches for the closest scrollable ancestor element and scrolls it so that selected element is inside its visible view port. If the element is already in the view port it doesn't do anything of course.

Robert Koritnik
  • 103,639
  • 52
  • 277
  • 404
  • 20
    @Brilliand: That is of course true, but that doesn't mean that they're not using it or are reluctant to using it. All I'm doing is providing an alternative. It's on OP to decide whether that's a path they'd take or not. Maybe they never considered using jQuery in the first place. – Robert Koritnik Jun 21 '12 at 08:32
  • That OP didn't specifically mention jQuery is utterly irrelevant: @RobertKoritnik's answer served me perfectly. – Dave Land Sep 02 '18 at 20:35
  • 1
    Incidentally, Bing is showing your code sample in the search results for "keep ancestor element from scrolling with scrollintoview." :-) – Dave Land Sep 02 '18 at 21:24
8

Adding more information to @Jesco post.

  • Element.scrollIntoViewIfNeeded() non-standard WebKit method for Chrome, Opera, Safari browsers.
    If the element is already within the visible area of the browser window, then no scrolling takes place.
  • Element.scrollIntoView() method scrolls the element on which it's called into the visible area of the browser window.

Try the below code in mozilla.org scrollIntoView() link. Post to identify Browser

var xpath = '//*[@id="Notes"]';
ScrollToElement(xpath);

function ScrollToElement(xpath) {
    var ele = $x(xpath)[0];
    console.log( ele );

    var isChrome = !!window.chrome && (!!window.chrome.webstore || !!window.chrome.runtime);
    if (isChrome) { // Chrome
        ele.scrollIntoViewIfNeeded();
    } else {
        var inlineCenter = { behavior: 'smooth', block: 'center', inline: 'start' };
        ele.scrollIntoView(inlineCenter);
    }
}
Yash
  • 9,250
  • 2
  • 69
  • 74
8

Just to add an answer as per my latest experience and working on VueJs. I found below piece of code ad best, which does not impact your application in anyways.

const el = this.$el.getElementsByClassName('your_element_class')[0];
if (el) {
   scrollIntoView(el,
                  {
                       block: 'nearest',
                       inline: 'start',
                       behavior: 'smooth',
                       boundary: document.getElementsByClassName('main_app_class')[0]
                    });
     }

main_app_class is the root class

your_element_class is the element/view where you can to scroll into

And for browser which does not support ScrollIntoView() just use below library its awesome https://www.npmjs.com/package/scroll-into-view-if-needed

Nishant Patel
  • 1,334
  • 14
  • 22
3

I found (in Chrome) I could more reliably scroll my element to the top of my parent div (without moving the page) if I scrolled from the bottom up to my element rather than from the top down to my element. Otherwise while my element would scroll into view, it would sometimes still be lower than desired within the div.

To achieve this, I am scrolling in two steps:

  1. myScrollableDiv.scrollTop = myScrollableDiv.scrollHeight which instantly scrolls to the bottom of my scrollable div
  2. (as per other answers here) Scroll my the element into view with animation:
myElementWithinTheScrollingDiv.scrollIntoView({
  behavior: 'smooth',
  block: 'nearest',
})
ABCD.ca
  • 2,365
  • 3
  • 32
  • 24
2

Using Brilliant's idea, here's a solution that only (vertically) scrolls if the element is NOT currently visible. The idea is to get the bounding box of the viewport and the element to be displayed in browser-window coordinate space. Check if it's visible and if not, scroll by the required distance so the element is shown at the top or bottom of the viewport.

    function ensure_visible(element_id)
    {
        // adjust these two to match your HTML hierarchy
        var element_to_show  = document.getElementById(element_id);
        var scrolling_parent = element_to_show.parentElement;

        var top = parseInt(scrolling_parent.getBoundingClientRect().top);
        var bot = parseInt(scrolling_parent.getBoundingClientRect().bottom);

        var now_top = parseInt(element_to_show.getBoundingClientRect().top);
        var now_bot = parseInt(element_to_show.getBoundingClientRect().bottom);

        // console.log("Element: "+now_top+";"+(now_bot)+" Viewport:"+top+";"+(bot) );

        var scroll_by = 0;
        if(now_top < top)
            scroll_by = -(top - now_top);
        else if(now_bot > bot)
            scroll_by = now_bot - bot;
        if(scroll_by != 0)
        {
            scrolling_parent.scrollTop += scroll_by; // tr.offsetTop;
        }
    }
KungPhoo
  • 516
  • 4
  • 18
2

ScrollIntoView() causes page movement. But the following code works fine for me and move the screen to the top of the element:

window.scroll({
  top: document.getElementById('your-element')?.offsetParent.offsetTop,
  behavior: 'smooth',
  block: 'start',
})
1

i had the same problem, i fixed it by removing the transform:translateY CSS i placed on the footer of the page.

yael kfir
  • 183
  • 3
  • 5
1

FWIW: I found (in Chrome 95, and Firefox 92 (all Mac)) that using:

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

on a scrollable list of options would scroll the body element a little, so I opted to use:

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

and select an option past the one I wanted centered (e.g. in a scrollable elem with 5 lines/options viewable, I selected the 2nd option past the one I wanted centered, thereby centering the desired element.

enter image description here

Laaouatni Anas
  • 4,199
  • 2
  • 7
  • 26
dougwig
  • 526
  • 6
  • 8