Scroll to element only if not in view - jQuery
Asked Answered
P

11

43

I know a variation on this has been asked several times; I've been browsing SO for a while now but either I'm doing something wrong or I haven't found what I need.

I have a structure of nested comments, pretty much the same as the Facebook Comments plugin, and whenever reply is clicked, a small form with textarea and a button appear at the bottom of the comments.

Again, the behaviour is the same as the Facebook Comments plugin and I want to achieve the same when it comes to scrolling the newly add textarea into view.

I've tried the scrollTo plugin, and it works smoothly but, even if I manually scroll to the very bottom of the page, the scroll animation will always reset the scroll position and begin from the top.

For the record, this is how I'm calling scrollTo:

$.scrollTo($('#addReply_1'), 800);

where addReply_1 is the div containing the form. I've stried scrolling to the form itself and to the textarea. Same results.

Is there a way to scroll to an element only if its not already visible?

I've tried many solutions offered on SO, like Scroll to an element using jQuery, but none seem to behave as desired; even Scroll to a particular element w/ jQuery or Check if element is visible after scrolling displays the same "jumpy" behaviour.


UPDATE: Online demo to show behaviour

I've uploaded an html demo page that shows the behaviour I'm complaining about: http://www.wouldbebetter.com/demo/comment-demo.htm

Just scroll to the bottom of the page and click any of the Reply links to see the "jumpy" scrolling I'm referring to.

Note that this demo uses the scrollintoview plugin of @Robert Koritnik's answer, but the behaviour is the same if I use, for instance, ScrollTo.

Parra answered 16/4, 2011 at 9:34 Comment(2)
check the offset of the element and test to see if it is within the bounds of the window.Bedim
@Bedim - Thanks, could you help a little on how to check if it's within bounds?Parra
E
32

Yes there is a jQuery plugin that scrolls to an element only if it's not within visible boundaries of the scrollable ancestor. I've written one does exactly what you require. And you will probably find it easier to use compared to scrollTo() since you only have to provide the element that you'd like to see.

I could copy paste the code here, but since I add some additions from time to time it's better to link you to blog post where you'll find all the details related to programmatic scrolling and latest plugin code. Programmatic scrolling can be quite distracting to users and the whole user interface experience, so I suppose it will be an interesting read.

Usage

Plugin is really simple to use:

$("#ElementToScrollIntoView").scrollintoview();

Plugin automatically finds nearest scrollable ancestor and scrolls it accordingly (if at all needed). There are some additional settings to this plugin you can use and this is how they look like:

scrollintoview: function (options) {
    /// <summary>Scrolls the first element in the set into view by scrolling its closest scrollable parent.</summary>
    /// <param name="options" type="Object">Additional options that can configure scrolling:
    ///        duration (default: "fast") - jQuery animation speed (can be a duration string or number of milliseconds)
    ///        direction (default: "both") - select possible scrollings ("vertical" or "y", "horizontal" or "x", "both")
    ///        complete (default: none) - a function to call when scrolling completes (called in context of the DOM element being scrolled)
    /// </param>
    /// <return type="jQuery">Returns the same jQuery set that this function was run on.</return>

I'm using this plugin on my Sharepoint 2010 site on pages where I present long tabular data. Whenever I add a new item (row) to this table I additionally scroll to this new record and highlight it, so users can see the new record immediately.

Sharepoint was also the reason why I decided not to provide scrollable element manually but rather look for it programatically. Sharepoint uses admin costumizable master pages which means I don't know which element is going to be scrollable at runtime. But I do know which element I want to see. Hence this plugin. It's also rather simplified compared to scrollTo() plugin that supports various different scenarios. Most of the time developers tend to use only one (or a very limited number of them).

Additional observations

Default link click processing prevention

Using my plugins still makes it rather problematic, since there's some flickering when adding those reply boxes. The problem is that your link clicking actually executes. You should prevent this in order to make your page to work smooth:

  1. either set click events on your links in one of these two ways:

    <a href="javascript:void AddReplyForm(44); return false;">Reply</a>
    

    or

    <a href="#" onclick="void AddReplyForm(44); return false;">Reply</a>
    
  2. a better way would be to run this on document ready:

    $(function() {
        $("a").click(function(evt) {
            evt.preventDefault();
        });
    });
    

The main idea is to prevent browser from processing link clicks. Because this makes the browser to look for in-page anchor and since it can't find one, it auto scrolls to the top. Then you tell it to scroll to your element.

Duplicate IDs

When you create reply form, you add new and new and new elements but they're all with the same ID. You should either avoid doing this or use some other means. You could remove the need for IDs altogether by providing the element to your BindClick() function. The main reply generating function could as well look like this (this function is written in a way that completely eliminates the need for element IDs):

function AddReplyForm(topCommentID)
{
    var el = $(addReplyForm).appendTo('#comment_' + topCommentID + ' .right');
    BindClick(el); // mind this !! you provide the element to remove
    el.scrollintoview();
}
Edvard answered 18/4, 2011 at 9:17 Comment(18)
@Robert - Nice plugin. I actually see the same behaviour: even if the page is scrolled so that the newly inserted form is in view the page scrolls from the top. Any ideas?Parra
@Sergi: In that case I suggest you provide some code and I'll provide a demo to actually see what's going on. This behaviour is not part of my plugin so I suppose you are either not testing correct page (where you used it) or haven't refreshed your scripts (Ctrl-F5) and your page still uses previous behaviour. Scrolling from top is not developed in my plugin at all. But there's also a third option: you're using some other functionality as well (by means of a plugin or directly manipulating DOM element properties or calling DOM functions) that does scrolling to top first.Edvard
@Robert - I've refreshed everything, that's for sure. I'm setting up a demo; I'll post the link. Thanks for your help. BTW, does your plugin depend on any specific version of jQuery? Using 1.4.4 right now but I'll upgrade to 1.5.1 shortly...Parra
@Sergi: Yes it does. And as the opening plugin comment says: *Requires jQuery 1.4 or newer" so you should be fine. And when you'll provide a demo just provide basic things where the problem can be reproduced so it will be easier for me to handle...Edvard
@Robert - I've added a link to a demo page (html & jQuery only). Thanks for your help (and patience ;)Parra
@Sergi: give me some time to examine what's going on here.Edvard
@Sergi Papaseit: Your demo page is missing... :(Edvard
@Robert - Oops. Sorry 'bout that. It's available again. :)Parra
@Sergi: But you know what? When resize window so the last comment is not visible and I call $(".comment:last").scrollintoview() it scrolls the last comment into view... Seems it works.Edvard
@Robert - Yes, that works actually very well indeed. The problem is when the last comment is already visible. Try this: scroll the page down completely, then click on one of the reply links: the page should not scroll, but it does, in an awkward way.Parra
@Sergi: Good news! You can download the latest almost completely rewritten plugin that works reliably across browsers even when scrolling the whole document (as is in your case). But regarding flickering you have some other problem, because if you run any of these $(".comment").scrollintoview() or $(".comment:last").scrollintoview() there's no flickering. As I've seen you're not disabling even bubbling on links click which should of course be done to prevent further processing (e.preventDefault() is your friend). Otherwise you should probably also bind events differently.Edvard
@Sergi Papaseit: I've resolved your problem with page flickering and invalid scrolling. Check my edited answer.Edvard
@Robert - e.preventDefault() worked indeed! Thank you very much for your time and effort; I'd upvote you a couple more times if it were allowed =) I'll download the latest version of your plug-in right away.Parra
@Robert - Just to be sure, the new version of the plugin can be found on your original blog post?Parra
@Sergi: Yes. The latest version (1.7 - 30th April 2011) can be found on my original blog post. Version is part of the code comment section at the top of plugin. Refresh your browser if you see cached content with version 1.6Edvard
Still works great in late 2015. You should throw it up on github and npm!Yolande
@mpen: It has been on Github since forever. :)Edvard
@RobertKoritnik Oh..awesome. Well then just put it on npm too so that we can install it and keep it up to date in a sane manner :DYolande
C
42

All modern Browsers support this. Visit: http://caniuse.com/#search=scrollIntoView

function scrollIntoViewIfNotVisible(target) { 
    if (target.getBoundingClientRect().bottom > window.innerHeight) {
        target.scrollIntoView(false);
    }
    
    if (target.getBoundingClientRect().top < 0) {
        target.scrollIntoView();
    } 
}

Update

The target has to be an Element. If you use jQuery call the function like this:

scrollIntoViewIfNotVisible($(".target")[0]);
Charlettecharley answered 15/6, 2016 at 8:10 Comment(9)
Uncaught TypeError: target.getBoundingClientRect is not a functionVisitant
@AeroWang Which browser and browser version?Charlettecharley
Chrome 60.0.3100.0Visitant
@Magu I already tried it yesterday and ultimately I don't think scrollIntoView works for mobile safari - especially when encountering the ghost margin probably for the entire html element (if using chinese pinyin 9 buttons input). Check out my question here: #43997035Visitant
In case the target is taller than the window, this could be improved by calling getBoundingClientRect() second time for rect.top after first scrollIntoView occured. This will allign the top of the target with an edge of the window, which is probably more desirable than alligning the bottom.Kei
Why are we stating that this is supported by all major browser even though in 2022 the mdn documentation states that it is: Non-standard: This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user. There may also be large incompatibilities between implementations and the behavior may change in the future. Personally my interpretation of 'supported by all major browsers' means 'safe to use in production'.Metrorrhagia
@GeorgeS Yes, Element.scrollIntoViewIfNeeded is not supported on all browsers, so we use Element.scrollIntoView as in this answer.Charlettecharley
Edited to rename the custom function name to prevent it being confused with the non-standard but currently existing scrollIntoViewIfNeeded() method.Barytes
@HashimAziz Did you mean to name the function scrollIntoViewIfNotVisible?Felicita
E
32

Yes there is a jQuery plugin that scrolls to an element only if it's not within visible boundaries of the scrollable ancestor. I've written one does exactly what you require. And you will probably find it easier to use compared to scrollTo() since you only have to provide the element that you'd like to see.

I could copy paste the code here, but since I add some additions from time to time it's better to link you to blog post where you'll find all the details related to programmatic scrolling and latest plugin code. Programmatic scrolling can be quite distracting to users and the whole user interface experience, so I suppose it will be an interesting read.

Usage

Plugin is really simple to use:

$("#ElementToScrollIntoView").scrollintoview();

Plugin automatically finds nearest scrollable ancestor and scrolls it accordingly (if at all needed). There are some additional settings to this plugin you can use and this is how they look like:

scrollintoview: function (options) {
    /// <summary>Scrolls the first element in the set into view by scrolling its closest scrollable parent.</summary>
    /// <param name="options" type="Object">Additional options that can configure scrolling:
    ///        duration (default: "fast") - jQuery animation speed (can be a duration string or number of milliseconds)
    ///        direction (default: "both") - select possible scrollings ("vertical" or "y", "horizontal" or "x", "both")
    ///        complete (default: none) - a function to call when scrolling completes (called in context of the DOM element being scrolled)
    /// </param>
    /// <return type="jQuery">Returns the same jQuery set that this function was run on.</return>

I'm using this plugin on my Sharepoint 2010 site on pages where I present long tabular data. Whenever I add a new item (row) to this table I additionally scroll to this new record and highlight it, so users can see the new record immediately.

Sharepoint was also the reason why I decided not to provide scrollable element manually but rather look for it programatically. Sharepoint uses admin costumizable master pages which means I don't know which element is going to be scrollable at runtime. But I do know which element I want to see. Hence this plugin. It's also rather simplified compared to scrollTo() plugin that supports various different scenarios. Most of the time developers tend to use only one (or a very limited number of them).

Additional observations

Default link click processing prevention

Using my plugins still makes it rather problematic, since there's some flickering when adding those reply boxes. The problem is that your link clicking actually executes. You should prevent this in order to make your page to work smooth:

  1. either set click events on your links in one of these two ways:

    <a href="javascript:void AddReplyForm(44); return false;">Reply</a>
    

    or

    <a href="#" onclick="void AddReplyForm(44); return false;">Reply</a>
    
  2. a better way would be to run this on document ready:

    $(function() {
        $("a").click(function(evt) {
            evt.preventDefault();
        });
    });
    

The main idea is to prevent browser from processing link clicks. Because this makes the browser to look for in-page anchor and since it can't find one, it auto scrolls to the top. Then you tell it to scroll to your element.

Duplicate IDs

When you create reply form, you add new and new and new elements but they're all with the same ID. You should either avoid doing this or use some other means. You could remove the need for IDs altogether by providing the element to your BindClick() function. The main reply generating function could as well look like this (this function is written in a way that completely eliminates the need for element IDs):

function AddReplyForm(topCommentID)
{
    var el = $(addReplyForm).appendTo('#comment_' + topCommentID + ' .right');
    BindClick(el); // mind this !! you provide the element to remove
    el.scrollintoview();
}
Edvard answered 18/4, 2011 at 9:17 Comment(18)
@Robert - Nice plugin. I actually see the same behaviour: even if the page is scrolled so that the newly inserted form is in view the page scrolls from the top. Any ideas?Parra
@Sergi: In that case I suggest you provide some code and I'll provide a demo to actually see what's going on. This behaviour is not part of my plugin so I suppose you are either not testing correct page (where you used it) or haven't refreshed your scripts (Ctrl-F5) and your page still uses previous behaviour. Scrolling from top is not developed in my plugin at all. But there's also a third option: you're using some other functionality as well (by means of a plugin or directly manipulating DOM element properties or calling DOM functions) that does scrolling to top first.Edvard
@Robert - I've refreshed everything, that's for sure. I'm setting up a demo; I'll post the link. Thanks for your help. BTW, does your plugin depend on any specific version of jQuery? Using 1.4.4 right now but I'll upgrade to 1.5.1 shortly...Parra
@Sergi: Yes it does. And as the opening plugin comment says: *Requires jQuery 1.4 or newer" so you should be fine. And when you'll provide a demo just provide basic things where the problem can be reproduced so it will be easier for me to handle...Edvard
@Robert - I've added a link to a demo page (html & jQuery only). Thanks for your help (and patience ;)Parra
@Sergi: give me some time to examine what's going on here.Edvard
@Sergi Papaseit: Your demo page is missing... :(Edvard
@Robert - Oops. Sorry 'bout that. It's available again. :)Parra
@Sergi: But you know what? When resize window so the last comment is not visible and I call $(".comment:last").scrollintoview() it scrolls the last comment into view... Seems it works.Edvard
@Robert - Yes, that works actually very well indeed. The problem is when the last comment is already visible. Try this: scroll the page down completely, then click on one of the reply links: the page should not scroll, but it does, in an awkward way.Parra
@Sergi: Good news! You can download the latest almost completely rewritten plugin that works reliably across browsers even when scrolling the whole document (as is in your case). But regarding flickering you have some other problem, because if you run any of these $(".comment").scrollintoview() or $(".comment:last").scrollintoview() there's no flickering. As I've seen you're not disabling even bubbling on links click which should of course be done to prevent further processing (e.preventDefault() is your friend). Otherwise you should probably also bind events differently.Edvard
@Sergi Papaseit: I've resolved your problem with page flickering and invalid scrolling. Check my edited answer.Edvard
@Robert - e.preventDefault() worked indeed! Thank you very much for your time and effort; I'd upvote you a couple more times if it were allowed =) I'll download the latest version of your plug-in right away.Parra
@Robert - Just to be sure, the new version of the plugin can be found on your original blog post?Parra
@Sergi: Yes. The latest version (1.7 - 30th April 2011) can be found on my original blog post. Version is part of the code comment section at the top of plugin. Refresh your browser if you see cached content with version 1.6Edvard
Still works great in late 2015. You should throw it up on github and npm!Yolande
@mpen: It has been on Github since forever. :)Edvard
@RobertKoritnik Oh..awesome. Well then just put it on npm too so that we can install it and keep it up to date in a sane manner :DYolande
H
23

Had the same problem... after reviewing a few answers, here's what I came up with for doing this... without pulling down another plugin.

function scrollIntoViewIfNeeded($target) {
    if ($target.position()) {
        if ($target.position().top < jQuery(window).scrollTop()){
            //scroll up
            $('html,body').animate({scrollTop: $target.position().top});
        }
        else if ($target.position().top + $target.height() >
            $(window).scrollTop() + (
                window.innerHeight || document.documentElement.clientHeight
            )) {
            //scroll down
            $('html,body').animate({scrollTop: $target.position().top -
                (window.innerHeight || document.documentElement.clientHeight)
                    + $target.height() + 15}
            );
        }
    }
}

The "15" on the last line is just extra padding - you might need to adjust it, or add it to the scroll up line.

EDIT: changed window.innerHeight to (window.innerHeight || document.documentElement.clientHeight) for IE < 8 support

Honestly answered 12/4, 2012 at 19:49 Comment(5)
@Honestly should not $("selector").position() be $("selector").offset() ?Tades
@Tades Could be; a lot's changed in 4 years. I do remember this is the actual code I used (obv. I changed the selector).Honestly
where do you get $target.position() from?Wombat
$target is a jQuery object, passed as a parameter to the method. position() is a method on that object. $<name> is a common convention for naming jQuery objects. @Wombat api.jquery.com/positionHonestly
You must replace position() with offset() (as @Honestly pointed out) to handle positioned elementsLippi
L
14

to make sure elem is in view inside a container:

let rectElem = elem.getBoundingClientRect(), rectContainer=container.getBoundingClientRect();
if (rectElem.bottom > rectContainer.bottom) elem.scrollIntoView(false);
if (rectElem.top < rectContainer.top) elem.scrollIntoView();
Laktasic answered 24/3, 2017 at 22:55 Comment(1)
This answer worked the best for me because it more accurately follows the scrolling of familiar elements like select.Kerge
A
9

Element.scrollIntoViewIfNeeded()

According to MDN Web Docs:

The Element.scrollIntoViewIfNeeded() method scrolls the current element into the visible area of the browser window if it's not already within the visible area of the browser window. If the element is already within the visible area of the browser window, then no scrolling takes place. This method is a proprietary variation of the standard Element.scrollIntoView() method.

Example (with jQuery):

$('#example')[0].scrollIntoViewIfNeeded();

Note: This feature is non-standard and is not on a standards track, so use caution before using this in production. See Can I use... for browser support.

Adrea answered 28/9, 2019 at 3:7 Comment(4)
Holy crap. This doesn't even come up on G search.Kenogenesis
Shame it doesn't support {behavior: "smooth"} like scrollIntoView() does.Majors
Whoa! Exactly what I needed!Brolly
This should not currently be used for user-facing sites and this answer doesn't make that anywhere near clear enough. The documentation for it literally has a warning at the top of the page: Non-standard: This feature is non-standard and is not on a standards track. Do not use it on production sites facing the Web: it will not work for every user. There may also be large incompatibilities between implementations and the behavior may change in the future.Barytes
W
2

I modified the answer from @iandisme a bit and wrapped it up as a tiny jquery plugin:

(function ($) {
'use strict';

$.fn.scrollToSimple = function ($target) {
    var $container = this.first();      // Only scrolls the first matched container

    var pos = $target.position(), height = $target.outerHeight();
    var containerScrollTop = $container.scrollTop(), containerHeight = $container.height();
    var top = pos.top + containerScrollTop;     // position.top is relative to the scrollTop of the containing element

    var paddingPx = containerHeight * 0.15;      // padding keeps the target from being butted up against the top / bottom of the container after scroll

    if (top < containerScrollTop) {     // scroll up                
        $container.scrollTop(top - paddingPx);
    }
    else if (top + height > containerScrollTop + containerHeight) {     // scroll down
        $container.scrollTop(top + height - containerHeight + paddingPx);
    }
};
})(jQuery);

I removed the calls to .animate since I was looking for instant scrolling. I also added the ability to scroll any (scrollable) container, rather than just the window. Example usage:

// scroll the window so #target  is visible
$(window).scrollToSimple( $("#target") );

// scroll the scrollable element #container so that #target is visible
$("#container").scrollToSimple( $("#target") );
Waltz answered 12/5, 2016 at 3:15 Comment(0)
D
1

I wanted to avoid scrollIntoViewIfNeeded because it's non standard. This works for me

element.scrollIntoView({ behavior: "smooth", block: "end", inline: "nearest" })
Doxia answered 15/7 at 17:36 Comment(0)
E
0

I don't know if I understood what you want but see if this is it, close to or if I'm totally lost:

<!DOCTYPE html>
<html>
    <head>
        <title>Scroll To Reply</title>
        <meta charset="utf-8" />
     <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js"></script>
        <script type="text/javascript">
            $(document).ready(function(){
                var $contextarea = $('#contextform textarea');
                $('a.reply').live('click',function(){
                    $(this).closest('p').css({'margin-top':'300px;'});
                    $('#contextform').appendTo($(this).closest('p'));
                    $('#contextform').slideDown(1000);//.css({'display':'block'});
                    $(this).closest('p').attr('id','scrolltome');
                    $('html,body').animate({slideDown: $('#scrolltome').offset().top}, 2000);
                });
                $('.sendreply').live('click',function(){
                    if($contextarea.val() == null || $contextarea.val() == '') {
                        alert('textarea is empty!');
                    } else {
                        $('#contextform').slideUp(800);
                    }
                });

                //                $('button').click(function() {
                //                    $('html,body').animate({scrollTop: $('#body').offset().top}, 2000);//scrolls to the top
                //                });

            });
        </script>
        <style type="text/css">
            body{
                font-size:25px;
                font-family: 'Arimo', Arial, sans-serif;
            }
            #contextform {
                display:none;
                width:500px;
                height: 150px;
                background: #0489B7;
                padding: 5px
            }
            #contextform textarea {
                width: 490px;
                height: 90px;
            }
        </style>
    </head>
    <body id="body">
        <h1>Scroll to reply</h1>
        <div id="contextform">
            <form method="post" action="">
                <textarea id="textarea"></textarea>
                <button type="button" class="sendreply">Send Reply</button>
            </form>
            <a name="myAnchor" id="myAnchor">anchor</a>
        </div>
        <ol>
            <?php for ($i = 0; $i < 20; $i++) { ?>
                <li><p>The quick brown fox jumps over the lazy dog
                        <a href="#scrolltome" class="reply">Reply</a>
                    </p></li>
            <?php } ?>
        </ol>
    </body>
</html>
Ekaterinburg answered 25/4, 2011 at 23:55 Comment(0)
S
0

Every answer here either seemed outdated and no longer working with modern versions of jQuery (probably due to changes in the position() and offset() functions for example) or too limited to work in the circumstances I needed them for. For example, none of the above answers seemed to work if your code was in an iframe.

One of the biggest things I noticed was all of them just used the normal height of the container object, which worked fine as long as the entire container object was visible in the window, but when your container object was the html object itself and the height extended far below what was shown, the scroll down portion of the code no longer worked. Instead, the algorithm needs to use the object's visible height on the screen for it to work properly (see Get the visible height of a div with jQuery).

I ended up writing my own solution that seems much more robust and works in more circumstances:

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.

Susy answered 19/6, 2019 at 16:10 Comment(0)
J
0

I use the answer from kofifus (https://mcmap.net/q/378401/-scroll-to-element-only-if-not-in-view-jquery), except in many cases I don't know what the container is so I use answers from (Find first scrollable parent) to find that out. I use jQuery UI so I have the .scrollParent() method available to me (you can find a port of it in the linked question if you need it). I also use the proprietary scrollIntoViewIfNeeded if present, which is in many modern browsers, so the custom code currently only is needed for FireFox and Opera Mini (and old browsers) (https://caniuse.com/#feat=scrollintoviewifneeded).

(code is TypeScript)

/**
 * Scroll the element into view if not already visible.
 *
 * https://caniuse.com/#feat=scrollintoviewifneeded
 * https://mcmap.net/q/378401/-scroll-to-element-only-if-not-in-view-jquery
 * https://mcmap.net/q/189454/-find-first-scrollable-parent
 */
public static ScrollIntoViewIfNeeded(element: Element): void
{
    if (element)
    {
        // Proprietary method, available in many modern browsers
        if ((<any>element).scrollIntoViewIfNeeded)
        {
            (<any>element).scrollIntoViewIfNeeded();
        }
        else
        {
            let $element = $(element);

            // jQuery UI scrollParent method available?
            if ($element.scrollParent)
            {
                let $parent = $(element).scrollParent();
                let rectElem = element.getBoundingClientRect();
                let rectContainer = $parent[0].getBoundingClientRect();

                if (rectElem.bottom > rectContainer.bottom || rectElem.top < rectContainer.top)
                {
                    element.scrollIntoView();
                }
            }
            else if (element.scrollIntoView)
            {
                element.scrollIntoView();
            }
        }
    }
}
Jesse answered 12/12, 2019 at 9:43 Comment(0)
L
-1

JQuery is not required for this.

This function just shows the specified element:

function scrollIntoView(elm) {
    if(elm) {
        let bnd=elm.getBoundingClientRect();
        if     (bnd.top<0                    ) { elm.scrollIntoView(true );      }
        else if(bnd.bottom>window.innerHeight) { elm.scrollIntoView(bnd.top<=0); }
        }
    return elm;
    }

The following more capable function allows a container of the desired element to be scrolled into view, also providing the ability to easily ensure that the entire container is made visible so that half-occluded visuals are avoided.

/**
  * Scroll the specified element into view, optionally first searching for a specific container and
  * first making that visible. This function does it's best to scroll the entire container into view
  * but ultimately ensures that as much of the element as fits in the viewport will be visible.
  *
  * #### Arguments:
  *
  *     elm (DOMElement)    The element to make visible.
  *     contag (string)     Optional name of a container tag. Ignored if blank/null/omitted.
  *     conprp (string)     Optional name of a container property to also match. Ignored if blank/null/omitted.
  *     conval (string)     Optional value of the container property. Ignored if `conprp` is not supplied; defaults to "" if omitted.
  */
function scrollIntoView(elm,contag,conprp,conval) {
    if(elm) {
        if(contag || conprp) {
            let con;
            if(conval==null) { conval=""; }
            for(con=elm; con!=null && con.tagName!="BODY"; con=con.parentNode) {
                if((!contag || contag==con.tagName) && (!conprp || con[conprp]==conval)) {
                    break;                                                                          // matched container tag and property
                    }
                }
            if(con) { scrollIntoView(con); }
            }

        let bnd=elm.getBoundingClientRect();

        if     (bnd.top<0                    ) { elm.scrollIntoView(true );      }
        else if(bnd.bottom>window.innerHeight) { elm.scrollIntoView(bnd.top<=0); }
        }
    return elm;
    }

This makes it easy to, for example, show this when scrolling up:

enter image description here

instead of this:

enter image description here

Leontineleontyne answered 9/7, 2017 at 3:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.