JDC
JDC

Reputation: 4385

Plain JavaScript - ScrollIntoView inside Div

I have the requirement to scroll a certain element inside a div (not a direct child) into view.


Basically I need the same functionality as ScrollIntoView provides, but for a specified parent (only this parent should scroll).
Additionally it is not possible for me to use any 3rd party libraries.


I am not quite sure on how to approach this problem, as I do very limited JavaScript development. Is there someone that could help me out?


I found this code that would do exactly what I need, but unfortunately it requires JQuery and I was not able to translate it to plain JavaScript.

Upvotes: 20

Views: 29683

Answers (6)

ZecKa
ZecKa

Reputation: 2934

Here is an updated version of @Jordan Maduro answer. To make it work also with horizontal scroll

typescript version here

function scrollParentToChildById(parentId, childId){
  const parent = document.getElementById(parentId);
  const child = document.getElementById(childId);
  scrollParentToChild(parent, child)
}
function scrollParentToChild(parent, child, threshold = 0) {
    // Where is the parent on page
    const parentRect = parent.getBoundingClientRect();
    // What can you see?
    const parentViewableArea = {
        height: parent.clientHeight,
        width: parent.clientWidth,
    };
    // Where is the child
    const childRect = child.getBoundingClientRect();
    // Is the child viewable?
    const isViewableVertically = childRect.top >= parentRect.top &&
        childRect.bottom <= parentRect.top + parentViewableArea.height;
    const isViewableHorizontally = childRect.left >= parentRect.left &&
        childRect.right <= parentRect.left + parentViewableArea.width;
    // if you can't see the child try to scroll parent
    if (!isViewableVertically || !isViewableHorizontally) {
        // Should we scroll using top or bottom? Find the smaller ABS adjustment
        const scrollTop = childRect.top - parentRect.top;
        const scrollBot = childRect.bottom - parentRect.bottom;
        const scrollLeft = childRect.left - parentRect.left;
        const scrollRight = childRect.right - parentRect.right;
        if (Math.abs(scrollTop) < Math.abs(scrollBot) && Math.abs(scrollLeft) < Math.abs(scrollRight)) {
            // we're nearer to the top and left of the list
            parent.scrollTo({
                top: parent.scrollTop + scrollTop - threshold,
                left: parent.scrollLeft + scrollLeft - threshold,
                behavior: 'smooth',
            });
        }
        else {
            // we're nearer to the bottom and right of the list
            parent.scrollTo({
                top: parent.scrollTop + scrollBot + threshold,
                left: parent.scrollLeft + scrollRight + threshold,
                behavior: 'smooth',
            });
        }
    }
}
#horizontal{
  display: flex;
  gap: 12px;
  max-width: 400px;
  overflow: auto;
}
#vertical{
  max-height: 100px;
  overflow: auto;
}
.element {
  width: 40px;
  height: 40px;
  background: red;
  flex-shrink: 0;
}
<h1>Demo</h1>
<h2>Horizontal</h2>
<button onclick="scrollParentToChildById('horizontal', 'firsth')">Scroll to first</button>
<button onclick="scrollParentToChildById('horizontal', 'lasth')">Scroll to last</button>
<div id="horizontal">
  <div class="element" id="firsth">1</div>
  <div class="element">2</div>
  <div class="element">3</div>
  <div class="element">4</div>
  <div class="element">5</div>
  <div class="element">6</div>
  <div class="element">7</div>
  <div class="element">8</div>
  <div class="element">9</div>
  <div class="element">10</div>
  <div class="element">11</div>
  <div class="element">12</div>
  <div class="element" id="lasth">13</div>
</div>

<h2>Vertical</h2>
<button onclick="scrollParentToChildById('vertical', 'firstv')">Scroll to first</button>
<button onclick="scrollParentToChildById('vertical', 'lastv')">Scroll to last</button>
<div id="vertical">
  <div class="element" id="firstv">1</div>
  <div class="element">2</div>
  <div class="element">3</div>
  <div class="element">4</div>
  <div class="element">5</div>
  <div class="element">6</div>
  <div class="element">7</div>
  <div class="element">8</div>
  <div class="element">9</div>
  <div class="element">10</div>
  <div class="element">11</div>
  <div class="element">12</div>
  <div class="element" id="lastv">13</div>
</div>

Upvotes: 0

i3ene
i3ene

Reputation: 109

If you want to center the child inside the parent element, regardless of the location on screen, you can use something like:

function centerInParent(parent, child) {
  var parentRect = parent.getBoundingClientRect();
  var childRect = child.getBoundingClientRect();
  
  parent.scrollLeft += (childRect.left - parentRect.left) - (parent.clientWidth / 2);
  parent.scrollTop += (childRect.top - parentRect.top) - (parent.clientHeight / 2);
}

But this requires that there is enough scrolling possible (up/down & left/right) inside the parent element.

Upvotes: 3

EnikiBeniki
EnikiBeniki

Reputation: 981

Try this. I'ts worked for me fine.

onArrowUp() {
    this.focus === null ? this.focus = 0 : (this.focus > 0 ? this.focus-- : null);
    this.items[this.focus].scrollIntoView({block: "nearest", behavior: "smooth"})
}

onArrowDown() {
    this.focus === null ? this.focus = 0 : (this.focus < this.items.length - 1 ? this.focus++ : null);        
    this.items[this.focus].scrollIntoView({block: "nearest", behavior: "smooth"})
}

Methods onListUp and onListDown should be called when pressed Up and Down arrows. The focus is current element id of elements array. The items is array of list DOM elements.

Upvotes: 0

Jordan Maduro
Jordan Maduro

Reputation: 998

I think I have a start for you. When you think about this problem you think about getting the child div into the viewable area of the parent. One naive way is to use the child position on the page relative to the parent's position on the page. Then taking into account the scroll of the parent. Here's a possible implementation.

function scrollParentToChild(parent, child) {

  // Where is the parent on page
  var parentRect = parent.getBoundingClientRect();
  // What can you see?
  var parentViewableArea = {
    height: parent.clientHeight,
    width: parent.clientWidth
  };

  // Where is the child
  var childRect = child.getBoundingClientRect();
  // Is the child viewable?
  var isViewable = (childRect.top >= parentRect.top) && (childRect.bottom <= parentRect.top + parentViewableArea.height);

  // if you can't see the child try to scroll parent
  if (!isViewable) {
        // Should we scroll using top or bottom? Find the smaller ABS adjustment
        const scrollTop = childRect.top - parentRect.top;
        const scrollBot = childRect.bottom - parentRect.bottom;
        if (Math.abs(scrollTop) < Math.abs(scrollBot)) {
            // we're near the top of the list
            parent.scrollTop += scrollTop;
        } else {
            // we're near the bottom of the list
            parent.scrollTop += scrollBot;
        }
  }

}

Just pass it the parent and the child node like this:

scrollParentToChild(parentElement, childElement)

Added a demo using this function on the main element and even nested elements

https://jsfiddle.net/nex1oa9a/1/

Upvotes: 50

Tobias Alt
Tobias Alt

Reputation: 483

You can do some easy stuff like:

function customScroll(id) {
    window.location.href = "#mydiv"+id;
}

Basically window.location.href should help you.

Upvotes: 0

Sumit
Sumit

Reputation: 4242

This functionality can be achieved in some few steps. First you get the position of the child using childElement.getBoundingClientRect(); which will return the following values

bottom : val
height: val
left: val
right: val
top: val
width: val

Then just position the child element according to the top left values into the parent element keeping child elements position as absolute. The parent Element's position must be relative type to place the child properly and get the effect of ScrollIntoView.

childElement.style.position = 'absolute';
childElement.style.top = 'value in px';
childElement.style.left = 'value in px';

Upvotes: 0

Related Questions