295

I have a scrolled div and I want to have an event when I click on it, it will force this div to scroll to view an element inside. I wrote its JavasSript like this:

document.getElementById(chr).scrollIntoView(true);

but this scrolls all the page while scrolling the div itself. How to fix that?

I want to say it like this: MyContainerDiv.getElementById(chr).scrollIntoView(true);

NPovlsen
  • 337
  • 1
  • 16
Amr Elgarhy
  • 66,568
  • 69
  • 184
  • 301
  • 1
    Scrolltop doesn't always return a usable value. I tend to use a `SetTimeout` in the `$(document).ready({})` function and set `focus()` to the element you want to scroll to. Works for me – DerpyNerd Aug 30 '16 at 06:44

18 Answers18

480

You need to get the top offset of the element you'd like to scroll into view, relative to its parent (the scrolling div container):

var myElement = document.getElementById('element_within_div');
var topPos = myElement.offsetTop;

The variable topPos is now set to the distance between the top of the scrolling div and the element you wish to have visible (in pixels).

Now we tell the div to scroll to that position using scrollTop:

document.getElementById('scrolling_div').scrollTop = topPos;

If you're using the prototype JS framework, you'd do the same thing like this:

var posArray = $('element_within_div').positionedOffset();
$('scrolling_div').scrollTop = posArray[1];

Again, this will scroll the div so that the element you wish to see is exactly at the top (or if that's not possible, scrolled as far down as it can so it's visible).

Jonathan Dumaine
  • 5,575
  • 3
  • 38
  • 50
Brian Barrett
  • 4,832
  • 2
  • 16
  • 3
  • 11
    This was really helpful! If you want to set the scroll multiple times you need to offset by your current scroll location. Here's how I did it in jQuery: $('#scrolling_div').scrollTop($('#scrolling_div').scrollTop() + $('#element_within_div').position().top); – Will Aug 14 '14 at 17:43
  • 1
    doesn't work anymore, offsetTop is always equals to 0 – singe3 Apr 03 '15 at 15:14
  • 8
    Beware that requesting `myElement.offsetTop` will trigger reflow (layout thrashing) which could be a [performance bottleneck](https://developers.google.com/web/fundamentals/performance/rendering/avoid-large-complex-layouts-and-layout-thrashing?hl=en#avoid-layout-thrashing) – Kestutis Aug 09 '16 at 19:34
  • 49
    Remember to set the scrolling parent with css: `position: relative` otherwise you will spend a lot of time debugging like I just did. – savedario Feb 04 '17 at 20:39
  • 2
    I had to set the `overflow-y` property to `scroll` for the parent element (`scrolling_div`), otherwise it wasn't working. The default css value for the `overflow` property is `auto` and even though it also makes manual scrolling possible, the js code wouldn't function (not even with `{psition: relative}`..) – Evgeniya Manolova Feb 26 '17 at 00:40
  • 2
    Still works in 2017. Additional info: .offsetTop might return 0. Then you should refer to a parent element and try again. I did that for tags `h4` then `div` then `article` tag and only `article` worked for me. – Fenio Nov 07 '17 at 11:28
  • @savedario It should be documented within this answer. – Thanh Nguyen Jun 18 '20 at 09:43
  • 1
    love you man - worth noting, add this CSS ... scroll-behavior: smooth; ... to make the scroll animated and smooth (add it to the div with the scrollbar) – danday74 Jul 16 '21 at 02:30
  • I really hate to do container scrolling this way in React since refs are the preferred approach. Oddly, when dealing with scrolling in contexts like tables, accessing DOM and string IDs appears to be the best way to achieve the desired scroll behavior without heavily abusing refs and causing issues. If you have a large number of potential scroll targets, this is the best. – bsplosion May 09 '23 at 18:21
85

Method 1 - Smooth scrolling to an element inside an element

var box = document.querySelector('.box'),
    targetElm = document.querySelector('.boxChild'); // <-- Scroll to here within ".box"

document.querySelector('button').addEventListener('click', function(){
   scrollToElm( box, targetElm , 600 );   
});


/////////////

function scrollToElm(container, elm, duration){
  var pos = getRelativePos(elm);
  scrollTo( container, pos.top , 2);  // duration in seconds
}

function getRelativePos(elm){
  var pPos = elm.parentNode.getBoundingClientRect(), // parent pos
      cPos = elm.getBoundingClientRect(), // target pos
      pos = {};

  pos.top    = cPos.top    - pPos.top + elm.parentNode.scrollTop,
  pos.right  = cPos.right  - pPos.right,
  pos.bottom = cPos.bottom - pPos.bottom,
  pos.left   = cPos.left   - pPos.left;

  return pos;
}
    
function scrollTo(element, to, duration, onDone) {
    var start = element.scrollTop,
        change = to - start,
        startTime = performance.now(),
        val, now, elapsed, t;

    function animateScroll(){
        now = performance.now();
        elapsed = (now - startTime)/1000;
        t = (elapsed/duration);

        element.scrollTop = start + change * easeInOutQuad(t);

        if( t < 1 )
            window.requestAnimationFrame(animateScroll);
        else
            onDone && onDone();
    };

    animateScroll();
}

function easeInOutQuad(t){ return t<.5 ? 2*t*t : -1+(4-2*t)*t };
.box{ width:80%; border:2px dashed; height:180px; overflow:auto; }
.boxChild{ 
  margin:600px 0 300px; 
  width: 40px;
  height:40px;
  background:green;
}
<button>Scroll to element</button>
<div class='box'>
  <div class='boxChild'></div>
</div>

Method 2 - Using Element.scrollIntoView:

var targetElm = document.querySelector('.boxChild'),  // reference to scroll target
    button = document.querySelector('button');        // button that triggers the scroll
  
// bind "click" event to a button 
button.addEventListener('click', function(){
   targetElm.scrollIntoView()
})
.box {
  width: 80%;
  border: 2px dashed;
  height: 180px;
  overflow: auto;
  scroll-behavior: smooth; /* <-- for smooth scroll */
}

.boxChild {
  margin: 600px 0 300px;
  width: 40px;
  height: 40px;
  background: green;
}
<button>Scroll to element</button>
<div class='box'>
  <div class='boxChild'></div>
</div>

Method 3 - Using CSS scroll-behavior:

.box {
  width: 80%;
  border: 2px dashed;
  height: 180px;
  overflow-y: scroll;
  scroll-behavior: smooth; /* <--- */
}

#boxChild {
  margin: 600px 0 300px;
  width: 40px;
  height: 40px;
  background: green;
}
<a href='#boxChild'>Scroll to element</a>
<div class='box'>
  <div id='boxChild'></div>
</div>
vsync
  • 118,978
  • 58
  • 307
  • 400
  • 2
    @sheats - it says exactly that in the MDN documentation link which I had placed in large font size. Even if it doesn't work for those browsers, it doesn't mean you shouldn't use it. There is no "rule" everything must behave the same on all browsers. If modern browsers can do magic, let them do magic. – vsync Feb 23 '19 at 21:36
  • 1
    This post is about scrolling the entire document rather than a scrollable element. –  Jul 29 '19 at 12:10
  • 3
    @DoMiNeLa10 - That's an assumption. OP might have provided an arbitrary example which meant to illustrate his/her issue. Also, OP is not the main concern but rather folks coming from search engines looking for a wholesome solution, and most likely answering specifically to OP needs will not help them, and the aim of this website is to create **solid** answers that can help as many as possible. *My answer provides both*. – vsync Jul 29 '19 at 18:40
  • Note: scroll-behavior is not supported in IE – fauverism May 06 '22 at 14:51
  • For the benefit of anyone arriving at this old answer, browser support for [Element.scrollIntoView](https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView) is now excellent across the board. – BenLeah Jul 25 '23 at 11:19
82

You would have to find the position of the element in the DIV you want to scroll to, and set the scrollTop property.

divElem.scrollTop = 0;

Update:

Sample code to move up or down

  function move_up() {
    document.getElementById('divElem').scrollTop += 10;
  }

  function move_down() {
    document.getElementById('divElem').scrollTop -= 10;
  }
kevhender
  • 4,285
  • 1
  • 13
  • 16
Glennular
  • 17,827
  • 9
  • 58
  • 77
32

Native JS, Cross Browser, Smooth Scroll (Update 2020)

Setting ScrollTop does give the desired result but the scroll is very abrupt. Using jquery to have smooth scroll was not an option. So here's a native way to get the job done that supports all major browsers. Reference - caniuse

// get the "Div" inside which you wish to scroll (i.e. the container element)
const El = document.getElementById('xyz');

// Lets say you wish to scroll by 100px, 
El.scrollTo({top: 100, behavior: 'smooth'});

// If you wish to scroll until the end of the container
El.scrollTo({top: El.scrollHeight, behavior: 'smooth'});

That's it!


And here's a working snippet for the doubtful -

document.getElementById('btn').addEventListener('click', e => {
  e.preventDefault();
  // smooth scroll
  document.getElementById('container').scrollTo({top: 175, behavior: 'smooth'});
});
/* just some styling for you to ignore */
.scrollContainer {
  overflow-y: auto;
  max-height: 100px;
  position: relative;
  border: 1px solid red;
  width: 120px;
}

body {
  padding: 10px;
}

.box {
  margin: 5px;
  background-color: yellow;
  height: 25px;
  display: flex;
  align-items: center;
  justify-content: center;
}

#goose {
  background-color: lime;
}
<!-- Dummy html to be ignored -->
<div id="container" class="scrollContainer">
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div id="goose" class="box">goose</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
</div>

<button id="btn">goose</button>

Update: As you can perceive in the comments, it seems that Element.scrollTo() is not supported in IE11. So if you don't care about IE11 (you really shouldn't, Microsoft is retiring IE11 in June 2022), feel free to use this in all your projects. Note that support exists for Edge! So you're not really leaving your Edge/Windows users behind ;)

Reference

Niket Pathak
  • 6,323
  • 1
  • 39
  • 51
  • 1
    `scrollTo()` may be supported in all major browsers for `Window` objects but isn't supported in IE or Edge for elements. – Tim Down Jan 03 '20 at 15:43
  • According to [caniuse](https://caniuse.com/#feat=mdn-api_window_scrollto), it is supported in IE11 and Edge. Haven't tested personally on these browsers, but it seems to be supported. – Niket Pathak Jan 03 '20 at 15:55
  • 1
    That's `window.scrollTo`, not `Element.scrollTo`. Try this in Edge, for example, and check the console: https://codepen.io/timdown/pen/abzVEMB – Tim Down Jan 03 '20 at 16:19
  • you are right. IE11 is not supported. However **Edge v76** [(ref)](https://caniuse.com/#feat=element-scroll-methods) and above seems to be supported – Niket Pathak Jan 06 '20 at 13:40
20

To scroll an element into view of a div, only if needed, you can use this scrollIfNeeded function:

function scrollIfNeeded(element, container) {
  if (element.offsetTop < container.scrollTop) {
    container.scrollTop = element.offsetTop;
  } else {
    const offsetBottom = element.offsetTop + element.offsetHeight;
    const scrollBottom = container.scrollTop + container.offsetHeight;
    if (offsetBottom > scrollBottom) {
      container.scrollTop = offsetBottom - container.offsetHeight;
    }
  }
}

document.getElementById('btn').addEventListener('click', ev => {
  ev.preventDefault();
  scrollIfNeeded(document.getElementById('goose'), document.getElementById('container'));
});
.scrollContainer {
  overflow-y: auto;
  max-height: 100px;
  position: relative;
  border: 1px solid red;
  width: 120px;
}

body {
  padding: 10px;
}

.box {
  margin: 5px;
  background-color: yellow;
  height: 25px;
  display: flex;
  align-items: center;
  justify-content: center;
}

#goose {
  background-color: lime;
}
<div id="container" class="scrollContainer">
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div id="goose" class="box">goose</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
  <div class="box">duck</div>
</div>

<button id="btn">scroll to goose</button>
mpen
  • 272,448
  • 266
  • 850
  • 1,236
12

Code should be:

var divElem = document.getElementById('scrolling_div');
var chElem = document.getElementById('element_within_div');
var topPos = divElem.offsetTop;
divElem.scrollTop = topPos - chElem.offsetTop;

You want to scroll the difference between child top position and div's top position.

Get access to child elements using:

var divElem = document.getElementById('scrolling_div'); 
var numChildren = divElem.childNodes.length;

and so on....

Preview
  • 35,317
  • 10
  • 92
  • 112
pgp
  • 121
  • 1
  • 2
  • 1
    Shouldn't the 2nd line actually read `var chElem = document.getElementById('element_within_div');` and the 3rd line read `var topPos = divElem.offsetTop;`? – jayp Mar 26 '16 at 18:06
11

We can resolve this problem without using JQuery and other libs.

I wrote following code for this purpose:

You have similar structure ->

<div class="parent">
  <div class="child-one">

  </div>
  <div class="child-two">

  </div>
</div>

JS:

scrollToElement() {
  var parentElement = document.querySelector('.parent');
  var childElement = document.querySelector('.child-two');

  parentElement.scrollTop = childElement.offsetTop - parentElement.offsetTop;
}

We can easily rewrite this method for passing parent and child as an arguments

spirito_libero
  • 1,206
  • 2
  • 13
  • 21
10

If you are using jQuery, you could scroll with an animation using the following:

$(MyContainerDiv).animate({scrollTop: $(MyContainerDiv).scrollTop() + ($('element_within_div').offset().top - $(MyContainerDiv).offset().top)});

The animation is optional: you could also take the scrollTop value calculated above and put it directly in the container's scrollTop property.

dlauzon
  • 1,241
  • 16
  • 23
6

None of other answer fixed my issue.

I played around with scrollIntoView arguments and managed to found a solution. Setting inline to start and block to nearest prevents parent element (or entire page) to scroll:

document.getElementById(chr).scrollIntoView({
   behavior: 'smooth',
   block: 'nearest',
   inline: 'start'
});
ulou
  • 5,542
  • 5
  • 37
  • 47
5

Another example of using jQuery and animate.

var container = $('#container');
var element = $('#element');

container.animate({
    scrollTop: container.scrollTop = container.scrollTop() + element.offset().top - container.offset().top
}, {
    duration: 1000,
    specialEasing: {
        width: 'linear',
        height: 'easeOutBounce'
    },
    complete: function (e) {
        console.log("animation completed");
    }
});
Hopefulee
  • 136
  • 2
  • 3
4

There are two facts :

1) Component scrollIntoView is not supported by safari.

2) JS framework jQuery can do the job like this:

parent = 'some parent div has css position==="fixed"' || 'html, body';

$(parent).animate({scrollTop: $(child).offset().top}, duration)
squaleLis
  • 6,116
  • 2
  • 22
  • 30
nickmit
  • 53
  • 6
3

Here's a simple pure JavaScript solution that works for a target Number (value for scrollTop), target DOM element, or some special String cases:

/**
 * target - target to scroll to (DOM element, scrollTop Number, 'top', or 'bottom'
 * containerEl - DOM element for the container with scrollbars
 */
var scrollToTarget = function(target, containerEl) {
    // Moved up here for readability:
    var isElement = target && target.nodeType === 1,
        isNumber = Object.prototype.toString.call(target) === '[object Number]';

    if (isElement) {
        containerEl.scrollTop = target.offsetTop;
    } else if (isNumber) {
        containerEl.scrollTop = target;
    } else if (target === 'bottom') {
        containerEl.scrollTop = containerEl.scrollHeight - containerEl.offsetHeight;
    } else if (target === 'top') {
        containerEl.scrollTop = 0;
    }
};

And here are some examples of usage:

// Scroll to the top
var scrollableDiv = document.getElementById('scrollable_div');
scrollToTarget('top', scrollableDiv);

or

// Scroll to 200px from the top
var scrollableDiv = document.getElementById('scrollable_div');
scrollToTarget(200, scrollableDiv);

or

// Scroll to targetElement
var scrollableDiv = document.getElementById('scrollable_div');
var targetElement= document.getElementById('target_element');
scrollToTarget(targetElement, scrollableDiv);
NoBrainer
  • 5,853
  • 1
  • 27
  • 27
1

given you have a div element you need to scroll inside, try this piece of code

document.querySelector('div').scroll(x,y)

this works with me inside a div with a scroll, this should work with you in case you pointed the mouse over this element and then tried to scroll down or up. If it manually works, it should work too

Amado Saladino
  • 2,114
  • 23
  • 25
  • It's good to show source API behind the solution: https://developer.mozilla.org/en-US/docs/Web/API/Element/scroll – wael32gh Sep 11 '21 at 09:33
0

User Animated Scrolling

Here's an example of how to programmatically scroll a <div> horizontally, without JQuery. To scroll vertically, you would replace JavaScript's writes to scrollLeft with scrollTop, instead.

JSFiddle

https://jsfiddle.net/fNPvf/38536/

HTML

<!-- Left Button. -->
<div style="float:left;">
    <!-- (1) Whilst it's pressed, increment the scroll. When we release, clear the timer to stop recursive scroll calls. -->
    <input type="button" value="«" style="height: 100px;" onmousedown="scroll('scroller',3, 10);" onmouseup="clearTimeout(TIMER_SCROLL);"/>
</div>
<!-- Contents to scroll. -->
<div id="scroller" style="float: left; width: 100px; height: 100px; overflow: hidden;">
    <!-- <3 -->
    <img src="https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-logo.png?v=9c558ec15d8a" alt="image large" style="height: 100px" />
</div>
<!-- Right Button. -->
<div style="float:left;">
    <!-- As (1). (Use a negative value of 'd' to decrease the scroll.) -->
    <input type="button" value="»" style="height: 100px;" onmousedown="scroll('scroller',-3, 10);" onmouseup="clearTimeout(TIMER_SCROLL);"/>
</div>

JavaScript

// Declare the Shared Timer.
var TIMER_SCROLL;
/** 
Scroll function. 
@param id  Unique id of element to scroll.
@param d   Amount of pixels to scroll per sleep.
@param del Size of the sleep (ms).*/
function scroll(id, d, del){
    // Scroll the element.
    document.getElementById(id).scrollLeft += d;
    // Perform a delay before recursing this function again.
    TIMER_SCROLL = setTimeout("scroll('"+id+"',"+d+", "+del+");", del);
 }

Credit to Dux.


Auto Animated Scrolling

In addition, here are functions for scrolling a <div> fully to the left and right. The only thing we change here is we make a check to see if the full extension of the scroll has been utilised before making a recursive call to scroll again.

JSFiddle

https://jsfiddle.net/0nLc2fhh/1/

HTML

<!-- Left Button. -->
<div style="float:left;">
    <!-- (1) Whilst it's pressed, increment the scroll. When we release, clear the timer to stop recursive scroll calls. -->
    <input type="button" value="«" style="height: 100px;" onclick="scrollFullyLeft('scroller',3, 10);"/>
</div>
<!-- Contents to scroll. -->
<div id="scroller" style="float: left; width: 100px; height: 100px; overflow: hidden;">
  <!-- <3 -->
  <img src="https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-logo.png?v=9c558ec15d8a" alt="image large" style="height: 100px" />
</div>
<!-- Right Button. -->
<div style="float:left;">
    <!-- As (1). (Use a negative value of 'd' to decrease the scroll.) -->
    <input type="button" value="»" style="height: 100px;" onclick="scrollFullyRight('scroller',3, 10);"/>
</div>

JavaScript

// Declare the Shared Timer.
var TIMER_SCROLL;
/** 
Scroll fully left function; completely scrolls  a <div> to the left, as far as it will go.
@param id  Unique id of element to scroll.
@param d   Amount of pixels to scroll per sleep.
@param del Size of the sleep (ms).*/
function scrollFullyLeft(id, d, del){
    // Fetch the element.
    var el = document.getElementById(id);
    // Scroll the element.
    el.scrollLeft += d;
    // Have we not finished scrolling yet?
    if(el.scrollLeft < (el.scrollWidth - el.clientWidth)) {
        TIMER_SCROLL = setTimeout("scrollFullyLeft('"+id+"',"+d+", "+del+");", del);
    }
}

/** 
Scroll fully right function; completely scrolls  a <div> to the right, as far as it will go.
@param id  Unique id of element to scroll.
@param d   Amount of pixels to scroll per sleep.
@param del Size of the sleep (ms).*/
function scrollFullyRight(id, d, del){
    // Fetch the element.
    var el = document.getElementById(id);
    // Scroll the element.
    el.scrollLeft -= d;
    // Have we not finished scrolling yet?
    if(el.scrollLeft > 0) {
        TIMER_SCROLL = setTimeout("scrollFullyRight('"+id+"',"+d+", "+del+");", del);
    }
}
Mapsy
  • 4,192
  • 1
  • 37
  • 43
0

This is what has finally served me

/** Set parent scroll to show element
 * @param element {object} The HTML object to show
 * @param parent {object} The HTML object where the element is shown  */
var scrollToView = function(element, parent) {
    //Algorithm: Accumulate the height of the previous elements and add half the height of the parent
    var offsetAccumulator = 0;
    parent = $(parent);
    parent.children().each(function() {
        if(this == element) {
            return false; //brake each loop
        }
        offsetAccumulator += $(this).innerHeight();
    });
    parent.scrollTop(offsetAccumulator - parent.innerHeight()/2);
}
0

I needed to scroll a dynamically loading element on a page so my solution was a little more involved.

This will work on static elements that are not lazy loading data and data being dynamically loaded.

const smoothScrollElement = async (selector: string, scrollBy = 12, prevCurrPos = 0) => {
    const wait = (timeout: number) => new Promise(resolve => setTimeout(resolve, timeout));
    const el = document.querySelector(selector) as HTMLElement;
    let positionToScrollTo = el.scrollHeight;
    let currentPosition = Math.floor(el.scrollTop) || 0;
    let pageYOffset = (el.clientHeight + currentPosition);
    if (positionToScrollTo == pageYOffset) {
        await wait(1000);
    }
    if ((prevCurrPos > 0 && currentPosition <= prevCurrPos) !== true) {
        setTimeout(async () => {
            el.scrollBy(0, scrollBy);
            await smoothScrollElement(selector, scrollBy, currentPosition);
        }, scrollBy);
    }
};
Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Hatem Jaber
  • 2,341
  • 2
  • 22
  • 38
0

After going through the headache of element I the most consistent for lazy loaded/infinte-paginated containers is scrollIntoView how it works:select the last item of the children and put into view. simple

this code example is from my scraping project

const lastChild = document.querySelector('[data-test-id="virtuoso-item-list"]').lastChild
lastChild.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" });

lastChild could be in any scrollable container

Martins
  • 1,130
  • 10
  • 26
-1

browser does scrolling automatically to an element that gets focus, so what you can also do it to wrap the element that you need to be scrolled to into <a>...</a> and then when you need scroll just set the focus on that a

Trident D'Gao
  • 18,973
  • 19
  • 95
  • 159