How to move a parent container's horizontal scroll position towards one of it's children elements nearest center?

Viewed 819

Ok, so imagine I have a very simple scenario here. A container <div> that has a CSS property of overflow-x: scroll, it is 500px wide (visible width without the overflow) and it has 9 children. Children 1 to 4 and 6 to 9 have width of 100px, and number 5 (center one) has a width of 200px for a total of 1200px (total scroll width).

<nav class="scrollable">
  <div style="width: 100px;">1</div>
  <div style="width: 100px;">2</div>
  <div style="width: 100px;">3</div>
  <div style="width: 100px;">4</div>
  <div style="width: 100px;">5</div>
  <div style="width: 100px;">6</div>
  <div style="width: 100px;">7</div>
  <div style="width: 100px;">8</div>
  <div style="width: 100px;">9</div>
</nav>
.scrollable {
  position: relative;
  display: flex;
  flex-shrink: 0;
  width: 500px;
  overflow-x: scroll;
}

.scrollable > div {
  flex-shrink: 0;
  text-align: center;
  cursor: pointer;
}

So far so good. So now the goal is whenever we click on one of these child <div>s (1 to 9), the scroll position of the parent element to move itself as close as possible towards the child that is being clicked, as I attempted to illustrate below. The child in lightblue color is the one that is currently selected:

Scrollbar container and it's children

As you can see, the scroll remains the same for elements 1-3 and 7-9, it shifts left and right for 4 and 6 and it goes to the absolute center for child number 5.

We can control the parent element's scroll position via the parent.scrollLeft property. In addition to that, we can get it's visible width or total with (including the overflow) with parent.clientWidth and parent.scrollWidth respectively.

In addition to that, each child element has it's own visible width, and .offsetLeft properly, which tells us how far from the parent's element left corner it is positioned.

With all that information in hand, I almost managed to achieve the original goal I have in mind.

// event handler for the inner tabs

document.body.addEventListener('click', function (event) {
  if (event.target.closest('.scrollable > div')) {
    // the parent <div>
    const parent = event.target.closest('.scrollable');
    // the selected/clicked <div>
    const selected = event.target.closest('.scrollable > div'); 
    
    // center the parent's scrollbar around the selected/clicked child
    parent.scrollLeft = selected.offsetLeft - (selected.offsetWidth * 2);
  }
});

Codepen example (working).

The only problem here though, is that this calculation only works if all of the elements have the same width. But as soon as I change the width of a single element, then the scrollbar is no longer aligning properly. As shown in the example below, the width of child number 5 is 200px, while the rest remain 100px:

Codepen example which illustrates the problem (not working).

So obviously const selected = event.target.closest('.scrollable > div'); is not the right formula I need to use here, but after spending hours on this and multiple variations, I can not figure out the right one.

Any help would be greatly appreciated. Thanks.


Solution:

Okay, I must admit this went way over my head, but after a ton of brain-storming and trial end error, here is what it needs to be done in the odd chance someone stumbles upon the same problem in the future.

  • First you get the parent.clientWidth, that is the visible area of the scrollable container (without the overflow), then subtract the clicked element's own width (.offsetWidth) from it.

  • Divide that value by two.

  • Finally, set the parent.scrollLeft to be equal to the clicked child's .offsetLeft and subtract the previous value we calculated from it.

const remainingSpace = parent.clientWidth - target.offsetWidth;
const spaceLeftAndRight = remainingSpace / 2;
parent.scrollLeft = target.offsetLeft - spaceLeftAndRight;
0 Answers
Related