206

I have an HTML document with images in a grid format using <ul><li><img.... The browser window has both vertical & horizontal scrolling.

Question: When I click on an image <img>, how then do I get the whole document to scroll to a position where the image I just clicked on is top:20px; left:20px ?

I've had a browse on here for similar posts...although I'm quite new to JavaScript, and want to understand how this is achieved for myself.

ROMANIA_engineer
  • 54,432
  • 29
  • 203
  • 199
Nasir
  • 4,785
  • 10
  • 35
  • 39

11 Answers11

437

There's a DOM method called scrollIntoView, which is supported by all major browsers, that will align an element with the top/left of the viewport (or as close as possible).

$("#myImage")[0].scrollIntoView();

On supported browsers, you can provide options:

$("#myImage")[0].scrollIntoView({
    behavior: "smooth", // or "auto" or "instant"
    block: "start" // or "end"
});

Alternatively, if all the elements have unique IDs, you can just change the hash property of the location object for back/forward button support:

$(document).delegate("img", function (e) {
    if (e.target.id)
        window.location.hash = e.target.id;
});

After that, just adjust the scrollTop/scrollLeft properties by -20:

document.body.scrollLeft -= 20;
document.body.scrollTop -= 20;
Flimm
  • 136,138
  • 45
  • 251
  • 267
Andy E
  • 338,112
  • 86
  • 474
  • 445
  • 2
    Thank you! 5 other SO articles and you nailed it perfectly. My use case was a container in a bootstrap dialog that need to scroll into view. – Stephen Patten Mar 26 '13 at 20:43
  • 11
    As a note: `$("#myImage")[0].scrollIntoView(false);` will align the element on the bottom of the window. – Philipp Apr 18 '14 at 12:39
  • 1
    Beware: the "smooth" behavior isn't widely supported, and `scrollIntoView` can be a confusing experience for users without some sort of animation. – Cody Duval Jun 01 '16 at 19:21
  • scrollIntoView doesn't work on older IE. see http://caniuse.com/#feat=scrollintoview – John Henckel Dec 06 '16 at 16:31
  • 1
    @JohnHenckel: that's incorrect, even IE 5.5 had `scrollIntoView` with the boolean parameter. I'll try and send a pull request for caniuse.com later. – Andy E Dec 07 '16 at 11:00
  • 1
    Note: Using Chrome, I couldn't scroll `` elements into view, instead had to scroll their parent (``) and it worked. – CrazyTim May 08 '18 at 01:54
  • This method, on nested `li` tags, was causing lateral scrolling, when I just want vertical scrolling. Adjusting `scrollLeft` after the fact seems to negate the scroll entirely. With the `animate` approach, you can specify just `scrollTop` or `scrollLeft` depending on the direction of scrolling you want. – mcw Jul 20 '18 at 13:53
  • Great that you added `[0]` after the JQuery object, that was what I was looking for. – MrMacvos Apr 02 '23 at 06:23
208

Since you want to know how it works, I'll explain it step-by-step.

First you want to bind a function as the image's click handler:

$('#someImage').click(function () {
    // Code to do scrolling happens here
});

That will apply the click handler to an image with id="someImage". If you want to do this to all images, replace '#someImage' with 'img'.

Now for the actual scrolling code:

  1. Get the image offsets (relative to the document):

    var offset = $(this).offset(); // Contains .top and .left
    
  2. Subtract 20 from top and left:

    offset.left -= 20;
    offset.top -= 20;
    
  3. Now animate the scroll-top and scroll-left CSS properties of <body> and <html>:

    $('html, body').animate({
        scrollTop: offset.top,
        scrollLeft: offset.left
    });
    
David Tang
  • 92,262
  • 30
  • 167
  • 149
  • 3
    This only works for simple layouts. It does not handle all cases the way the W3C `.scrollIntoView` method does when elements are nested within multiple overflowing containers. – natevw Jan 14 '15 at 19:31
  • 7
    why to reinvent the wheel? `Element.scrollIntoView()`: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView – serge Nov 19 '18 at 11:40
  • 1
    @Serge because it's a working draft that hasn't been implemented in all browsers – John Smith Apr 16 '19 at 08:01
  • @JohnSmith please read attentively the link I posted, the "Browser Compatibility" section, you will see there is no browser do not support the `scrollIntoView` – serge Apr 16 '19 at 09:02
  • @Serge Sorry about that, I didn't realize, there was also scrollIntoViewOption (which is the one that is not supported wide enough). I guess it's good enough. – John Smith Apr 16 '19 at 10:43
  • @serge I read attentively and it clearly says this is experimental and to expect behavior to change in the future. Hard to use this in production confidently. – Priyanker Rao Sep 02 '21 at 08:18
21

Simplest solution I have seen

var offset = $("#target-element").offset();
$('html, body').animate({
    scrollTop: offset.top,
    scrollLeft: offset.left
}, 1000);

Tutorial Here

jpaugh
  • 6,634
  • 4
  • 38
  • 90
Matt Watson
  • 251
  • 2
  • 6
  • At the risk of making it less simple, I edited this to support horizontal scrolling as well. – jpaugh Jul 30 '19 at 18:24
8

There are methods to scroll element directly into the view, but if you want to scroll to a point relative from an element, you have to do it manually:

Inside the click handler, get the position of the element relative to the document, subtract 20 and use window.scrollTo:

var pos = $(this).offset();
var top = pos.top - 20;
var left = pos.left - 20;
window.scrollTo((left < 0 ? 0 : left), (top < 0 ? 0 : top));
Herc
  • 527
  • 4
  • 9
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • Only one with worked for me with the webpage I have. I wanted my element centered so I changed the top & left assignments to `var top = pos.top - window.innerHeight / 2; var left = pos.left - window.innerWidth / 2;` – Jon Nov 06 '22 at 10:19
7

Have a look at the jQuery.scrollTo plugin. Here's a demo.

This plugin has a lot of options that go beyond what native scrollIntoView offers you. For instance, you can set the scrolling to be smooth, and then set a callback for when the scrolling finishes.

You can also have a look at all the JQuery plugins tagged with "scroll".

Flimm
  • 136,138
  • 45
  • 251
  • 267
Rob Stevenson-Leggett
  • 35,279
  • 21
  • 87
  • 141
  • I was getting different offsets/positions in every browser in my case, and this plugin helped fixed it for me! thanks! – LaurelT Sep 08 '15 at 21:55
6

Here's a quick jQuery plugin to map the built in browser functionality nicely:

$.fn.ensureVisible = function () { $(this).each(function () { $(this)[0].scrollIntoView(); }); };

...

$('.my-elements').ensureVisible();
Rob
  • 1,687
  • 3
  • 22
  • 34
4

After trial and error I came up with this function, works with iframe too.

function bringElIntoView(el) {    
    var elOffset = el.offset();
    var $window = $(window);
    var windowScrollBottom = $window.scrollTop() + $window.height();
    var scrollToPos = -1;
    if (elOffset.top < $window.scrollTop()) // element is hidden in the top
        scrollToPos = elOffset.top;
    else if (elOffset.top + el.height() > windowScrollBottom) // element is hidden in the bottom
        scrollToPos = $window.scrollTop() + (elOffset.top + el.height() - windowScrollBottom);
    if (scrollToPos !== -1)
        $('html, body').animate({ scrollTop: scrollToPos });
}
Tone Škoda
  • 1,463
  • 16
  • 20
3

My UI has a vertical scrolling list of thumbs within a thumbbar The goal was to make the current thumb right in the center of the thumbbar. I started from the approved answer, but found that there were a few tweaks to truly center the current thumb. hope this helps someone else.

markup:

<ul id='thumbbar'>
    <li id='thumbbar-123'></li>
    <li id='thumbbar-124'></li>
    <li id='thumbbar-125'></li>
</ul>

jquery:

// scroll the current thumb bar thumb into view
heightbar   = $('#thumbbar').height();
heightthumb = $('#thumbbar-' + pageid).height();
offsetbar   = $('#thumbbar').scrollTop();


$('#thumbbar').animate({
    scrollTop: offsetthumb.top - heightbar / 2 - offsetbar - 20
});
xeo
  • 807
  • 2
  • 7
  • 25
  • I think you need to add in: offsetthumb = $('#thumbbar-' + pageid).offset(); And also replace the object with: scrollTop: offsetthumb.top - heightbar / 2 - heightthumb / 2 + offsetbar – Dave Jan 18 '21 at 18:07
2

Just a tip. Works on firefox only

Element.scrollIntoView();

wp student
  • 755
  • 9
  • 24
1

After trying to find a solution that handled every circumstance (options for animating the scroll, padding around the object once it scrolls into view, works even in obscure circumstances such as in an iframe), I finally ended up writing my own solution to this. Since it seems to work when many other solutions failed, I thought I'd share it:

function scrollIntoViewIfNeeded($target, options) {

    var options = options ? options : {},
    $win = $($target[0].ownerDocument.defaultView), //get the window object of the $target, don't use "window" because the element could possibly be in a different iframe than the one calling the function
    $container = options.$container ? options.$container : $win,        
    padding = options.padding ? options.padding : 20,
    elemTop = $target.offset().top,
    elemHeight = $target.outerHeight(),
    containerTop = $container.scrollTop(),
    //Everything past this point is used only to get the container's visible height, which is needed to do this accurately
    containerHeight = $container.outerHeight(),
    winTop = $win.scrollTop(),
    winBot = winTop + $win.height(),
    containerVisibleTop = containerTop < winTop ? winTop : containerTop,
    containerVisibleBottom = containerTop + containerHeight > winBot ? winBot : containerTop + containerHeight,
    containerVisibleHeight = containerVisibleBottom - containerVisibleTop;

    if (elemTop < containerTop) {
        //scroll up
        if (options.instant) {
            $container.scrollTop(elemTop - padding);
        } else {
            $container.animate({scrollTop: elemTop - padding}, options.animationOptions);
        }
    } else if (elemTop + elemHeight > containerTop + containerVisibleHeight) {
        //scroll down
        if (options.instant) {
            $container.scrollTop(elemTop + elemHeight - containerVisibleHeight + padding);
        } else {
            $container.animate({scrollTop: elemTop + elemHeight - containerVisibleHeight + padding}, options.animationOptions);
        }
    }
}

$target is a jQuery object containing the object you wish to scroll into view if needed.

options (optional) can contain the following options passed in an object:

options.$container - a jQuery object pointing to the containing element of $target (in other words, the element in the dom with the scrollbars). Defaults to the window that contains the $target element and is smart enough to select an iframe window. Remember to include the $ in the property name.

options.padding - the padding in pixels to add above or below the object when it is scrolled into view. This way it is not right against the edge of the window. Defaults to 20.

options.instant - if set to true, jQuery animate will not be used and the scroll will instantly pop to the correct location. Defaults to false.

options.animationOptions - any jQuery options you wish to pass to the jQuery animate function (see http://api.jquery.com/animate/). With this, you can change the duration of the animation or have a callback function executed when the scrolling is complete. This only works if options.instant is set to false. If you need to have an instant animation but with a callback, set options.animationOptions.duration = 0 instead of using options.instant = true.

dallin
  • 8,775
  • 2
  • 36
  • 41
0

Simple 2 steps for scrolling down to end or bottom.

Step1: get the full height of scrollable(conversation) div.

Step2: apply scrollTop on that scrollable(conversation) div using the value obtained in step1.

var fullHeight = $('#conversation')[0].scrollHeight;

$('#conversation').scrollTop(fullHeight);

Above steps must be applied for every append on the conversation div.