imhere
imhere

Reputation: 5005

ScrollIntoView() causing the whole page to move

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.

Upvotes: 263

Views: 239057

Answers (17)

Mohmmad Ebrahimi Aval
Mohmmad Ebrahimi Aval

Reputation: 482

I wrote this and solved my problem.

function scrollSmoothTargetElmToCenterOfparentElm(parentElm, targetElm) {
    const parentElmHalfOfHeight = parentElm.offsetHeight / 2
    const activeElmHalfOfHeight = targetElm.offsetHeight / 2
    const finalScrollPosition =
        targetElm.offsetTop - parentElm.offsetTop - parentElmHalfOfHeight + activeElmHalfOfHeight
    let currentScrollPosition = parentElm.scrollTop
    const INT = setInterval(function () {
        currentScrollPosition++
        parentElm.scrollTop = currentScrollPosition
        if (currentScrollPosition + 1 >= finalScrollPosition) clearInterval(INT)
    })
}

// sample of usage
const parentElm = document.getElementById("parent")
const targetElm = document.getElementById("target")
scrollSmoothTargetElmToCenterOfparentElm(parentElm, targetElm)

Upvotes: 0

Ben Schenker
Ben Schenker

Reputation: 939

Spent too many hours on a similar issue, the only thing that worked was using overflow: clip (instead of hidden) on the container element that was scrolling too far.

Quoting from the MDN docs

clip: Overflow content is clipped at the element's overflow clip edge that is defined using the overflow-clip-margin property. As a result, content overflows the element's padding box by the value of overflow-clip-margin or by 0px if not set. Overflow content outside the clipped region is not visible, user agents do not add a scroll bar, and programmatic scrolling is also not supported. No new formatting context is created. To establish a formatting context, use overflow: clip along with display: flow-root. The element box is not a scroll container.

HT to this comment above for suggesting it.

Upvotes: 2

dougwig
dougwig

Reputation: 616

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

Upvotes: 5

ABCD.ca
ABCD.ca

Reputation: 2495

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',
})

Upvotes: 4

Michal Tsadok
Michal Tsadok

Reputation: 153

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);
    }
});

Upvotes: 10

Cristiane Ferreira
Cristiane Ferreira

Reputation: 91

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',
})

Upvotes: 2

Nishant Patel
Nishant Patel

Reputation: 1406

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

Upvotes: 10

Gui Seek
Gui Seek

Reputation: 461

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'
});

Upvotes: 16

Yash
Yash

Reputation: 9568

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);
    }
}

Upvotes: 8

paddotk
paddotk

Reputation: 1440

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.

Upvotes: 23

jfrohn
jfrohn

Reputation: 3649

Fixed it with:

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

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

Upvotes: 364

dipole_moment
dipole_moment

Reputation: 5854

var el = document.querySelector("yourElement");
window.scroll({top: el.offsetTop, behavior: 'smooth'});

Upvotes: 24

KungPhoo
KungPhoo

Reputation: 774

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;
        }
    }

Upvotes: 2

yael kfir
yael kfir

Reputation: 183

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

Upvotes: 1

Jesco
Jesco

Reputation: 151

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

Upvotes: 15

Brilliand
Brilliand

Reputation: 13714

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;

Upvotes: 188

Robert Koritnik
Robert Koritnik

Reputation: 105029

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.

Upvotes: 8

Related Questions