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.
<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();
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
.
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:
instead of this:
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>
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.
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).
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:
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>
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();
}
I use the answer from kofifus (https://stackoverflow.com/a/43010437/1075062), 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://stackoverflow.com/questions/5685589/scroll-to-element-only-if-not-in-view-jquery
* https://stackoverflow.com/questions/35939886/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();
}
}
}
}