Skip to content

Instantly share code, notes, and snippets.

@jpaugh
Last active July 30, 2019 20:23
Show Gist options
  • Save jpaugh/265bbaadd7d3d243d2d52e558863847d to your computer and use it in GitHub Desktop.
Save jpaugh/265bbaadd7d3d243d2d52e558863847d to your computer and use it in GitHub Desktop.
An implementation of scrollIntoView which actually does the thing.
/**
* function scrollIntoViewSensibly
* Author: Jonathan Paugh; MIT License
* Source: https://gist.github.com/jpaugh/265bbaadd7d3d243d2d52e558863847d
*
* Scroll the parent element until the child is visible. This is like the 'nearest' option to [Element.scrollIntoView],
* except that it actually works --- even in IE, and even when the child is partially visible already. It's not a drop-in
* replacement, though: to make a polyfil for [Element.scrollIntoView], you'd want to find the nearest scrollable parent
* yourself, and then accept that all options passed to the polyfil would be ignored.
*
* 1. Only scrolls if necessary
* 2. If the child is bigger than the parent, its top/left edge is lined up with its parent's top/left edge (i.e. start)
* 2. Otherwise, the parent scrolls just enough to bring the child into view (i.e nearest)
* In other words, the furthest edge of the child is aligned to the nearest edge of the parent
*
* Relies on jQuery for animation, and to get an element's position relative to its parent
*
* [Element.scrollIntoView]: https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollIntoView
*/
function scrollIntoViewSensibly(parent, child, debug) {
function getScrollProperties(element) {
jPos = $(element).position()
return {
position: {
left: jPos.left,
top: jPos.top,
right: jPos.left + element.clientWidth,
bottom:jPos.top + element.clientHeight
},
client: {
width: element.clientWidth,
height: element.clientHeight
},
scroll: {
left: element.scrollLeft,
top: element.scrollTop,
right: element.scrollLeft + element.clientWidth,
bottom:element.scrollTop + element.clientHeight
}
}
}
var pMetrics = getScrollProperties(parent)
var cMetrics = getScrollProperties(child)
var newScroll = pMetrics.scroll;
//console.log("pMetrics", pMetrics);
//console.log("cMetrics", cMetrics);
// Do scrolling in a single dimension (e.g. "height") from the start (e.g. "top") to the end (e.g. "end")
function doScrolling(dimen, debug) {
if (debug) debug = console
else debug = {log: function () {}}
var start, end, msg;
switch (dimen) {
case "width":
start = "left"; end = "right"; msg = "horizontally";
break;
case "height":
start = "top"; end = "bottom"; msg = "vertically";
default:
console.error("Invalid scroll dimension " + dimen + ". \"width\" or \"height expected\"" );
}
if (cMetrics.client[dimen] > pMetrics.client[dimen]) {
// Child is bigger than parent; scroll parent to child start
debug.log("child bigger than parent", msg)
newScroll[start] = cMetrics.position[start];
} else if (cMetrics.position[start] < pMetrics.scroll[start]) {
// Start of child is hidden; align child start to parent start
debug.log("child starts before parent", msg)
newScroll[start] = cMetrics.position[start];
} else if (cMetrics.position[end] > pMetrics.scroll[end]) {
// End of child is hidden; align child end to parent end
// Conceptually, this would be:
// newScroll[end] = cMetrcis.position[end];
debug.log("child ends after parent", msg)
newScroll[start] = cMetrics.position[start] - (pMetrics.client[dimen] - cMetrics.client[dimen]);
} else {
debug.log("child is visible", msg)
}
}
doScrolling("width", debug);
doScrolling("height", debug);
//console.log("newScroll", newScroll);
$(parent).animate({
scrollLeft: newScroll.left,
scrollTop: newScroll.top
});
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment