Even though I have been staring at the JS logic for hours and cannot see anything at all wrong with it, the issue, if not caused by the CSS scroll snapping, must be in the JS.
Intended Behaviour: As the list with the grey background at the bottom of the page is scrolled horizontally, the coloured backgrounds above the grey list should be scrolling in correspondence with it. For example, when the yellow list element ("of
") snaps to the start
, so too should the yellow background align with the left of the "viewport."
The Issue: The rate at which the coloured backgrounds scroll are different from those of the list elements.
Is scroll-snap-align
the culprit or am I blind or missing something?
The demo below is a simplified version of the issue.
const
listCont = document.querySelector('#list-container'),
cssVarSpacing = 30,
bgCont = document.querySelector('#bg-container')
let
scrollRatio = null,
focusedEl = listCont.children[0],
lastScrollPos = 0
listCont.addEventListener('scroll', ()=>{
let
sibToFocus = null
scrollRatio = 240 / (focusedEl.getBoundingClientRect().width + cssVarSpacing)
// if scrolling to right
if (listCont.scrollLeft > lastScrollPos) {
if (focusedEl.getBoundingClientRect().right <= 0) sibToFocus = focusedEl.nextElementSibling
// if scrolling to left
} else if (focusedEl.getBoundingClientRect().left >= cssVarSpacing) sibToFocus = focusedEl.previousElementSibling
// if focusedEl is not first or last child, switch focusedEl to previous/next sibling once prior focusedEl reaches scroll-snap-align: start position
if (sibToFocus) focusedEl = sibToFocus
// scroll coloured backgrounds in accordance to the scroll ratio of the focusedEl scroll distance to the "viewport" scroll distance
bgCont.scrollTo(listCont.scrollLeft * scrollRatio, 0)
lastScrollPos = listCont.scrollLeft
})
main {width:240px;height:180px;position:relative;font-family:arial;font-size:20px;box-sizing:border-box}
#bg-container {
width: 100%;
height: 100%;
position: relative;
overflow-x: hidden;
display: grid;
grid-auto-flow: column;
grid-auto-columns: 100%;
}
#bg-container > div {width: 100%; height: 100%}
#bg-container > div:nth-child(1) {background: red}
#bg-container > div:nth-child(2) {background: orange}
#bg-container > div:nth-child(3) {background: yellow}
#bg-container > div:nth-child(4) {background: green}
#bg-container > div:nth-child(5) {background: blue}
#bg-container > div:nth-child(6) {background: indigo}
#list-container {
--spacing: 30px;
width: 240px;
position: absolute;
top: 100%;
transform: translateY(-100%);
padding: var(--spacing);
scroll-padding-inline-start: var(--spacing);
overflow-x: auto;
scroll-snap-type: x mandatory;
opacity: .85;
display: grid;
grid-auto-flow: column;
grid-auto-columns: min-content;
gap: var(--spacing);
box-sizing: border-box;
background: grey;
}
#list-container > div {padding: calc(var(--spacing) / 2) var(--spacing); scroll-snap-align: start; white-space: nowrap; border-radius: var(--spacing); box-sizing:border-box;}
#list-container > div:nth-child(1) {background: red; color: black;}
#list-container > div:nth-child(2) {background: orange; color: black;}
#list-container > div:nth-child(3) {background: yellow; color: black;}
#list-container > div:nth-child(4) {background: green; color: white;}
#list-container > div:nth-child(5) {background: blue; color: white;}
#list-container > div:nth-child(6) {background: indigo; color: white;}
#list-container > div:last-child {
position: relative;
}
#list-container > div:last-child::after {
content: '';
width: calc(240px - 100% - var(--spacing));
height: 1px;
position: absolute;
top: 0;
left: 100%;
pointer-events: none;
opacity:0;
}
<main>
<div id='bg-container'>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
<div></div>
</div>
<div id='list-container'>
<div>the</div>
<div>widths</div>
<div>of</div>
<div>these</div>
<div>elements</div>
<div>varies</div>
</div>
</main>