With a parent element having scroll-behavior: smooth; I am trying to scroll it when child elements are clicked so that the child is fully visible in the parent. This works fine if it only needs to scroll horizontally or vertically, but fails when it has to do both (e.g. the child is in the lower right corner and only partially visible). The click handler is figuring out what values to add/subtract to the parent's scrollTop and scrollLeft attributes. I note that if I put a setTimeout() with a delay greater than the scroll transition time around setting scrollLeft (but not scrollTop) then it works (but it looks hokey). Is there any way to do both X and Y smooth scrolling at the same time?
Here's some pseudo-code so you can get the flavor of what I'm doing:
HTML
<div id="grid">
<div class="row">
<div id="cell-1" class="cell"> 1 </div>
<div id="cell-2" class="cell"> 2 </div>
...
</div>
...
</div>
CSS
#grid {
height: 100vh;
overflow: scroll;
scroll-behavior: smooth;
}
.row {
display: flex;
}
.cell {
flex: 1 0 auto;
border: 2px dashed gray;
width: 250px;
height: 100px;
margin: 5px;
padding: 10px;
}
JS
let grid = document.querySelector('#grid');
document.addEventListener('click', function (e) {
if (e.target.classList.contains('cell')) {
... (omitted a bunch of code to obtain cell/grid X/Y bounds) ...
if (cellRightBound > gridRightBound) {
let scroll = cellRightBound - gridRightBound + halfCellWidth;
grid.scrollLeft += scroll;
console.log('scrolled right ' + scroll);
} else if (cellLeftBound < gridLeftBound) {
let scroll = gridLeftBound - cellLeftBound + halfCellWidth;
if (scroll > 0) {
grid.scrollLeft -= scroll;
console.log('scrolled left ' + scroll);
}
}
if (cellBottomBound > gridBottomBound) {
let scroll = cellBottomBound - gridBottomBound + halfCellHeight;
grid.scrollTop += scroll;
console.log('scrolled down ' + scroll);
} else if (cellTopBound < gridTopBound) {
let scroll = gridTopBound - cellTopBound + halfCellHeight;
if (scroll > 0) {
grid.scrollTop -= scroll;
console.log('scrolled up ' + scroll);
}
}
}
});
So the answer is to use grid.scrollTo({left: scrollLeft, top: scrollTop}), and set the variables scrollLeft and scrollTop the same as grid.scrollLeft and grid.scrollTop were being set. E.g.:
let scrollLeft, scrollTop;
if (cellRightBound > gridRightBound) {
let scroll = cellRightBound - gridRightBound + halfCellWidth;
scrollLeft = grid.scrollLeft + scroll;
console.log('scrolled right ' + scroll);
} else if (cellLeftBound < gridLeftBound) {
let scroll = gridLeftBound - cellLeftBound + halfCellWidth;
if (scroll > 0) {
scrollLeft = grid.scrollLeft - scroll;
console.log('scrolled left ' + scroll);
}
}
if (cellBottomBound > gridBottomBound) {
let scroll = cellBottomBound - gridBottomBound + halfCellHeight;
scrollTop = grid.scrollTop + scroll;
console.log('scrolled down ' + scroll);
} else if (cellTopBound < gridTopBound) {
let scroll = gridTopBound - cellTopBound + halfCellHeight;
if (scroll > 0) {
scrollTop -= grid.scrollTop - scroll;
console.log('scrolled up ' + scroll);
}
}
if (scrollLeft !== undefined || scrollTop !== undefined) {
if (scrollLeft === undefined)
scrollLeft = grid.scrollLeft;
if (scrollTop === undefined)
scrollTop = grid.scrollTop;
grid.scrollTo({left: scrollLeft, top: scrollTop, behavior: 'smooth'});
}