In the JavaScript, we start off in the same way as with the previous example, except that this time we generate 49 <section> elements, and we give each one an ID of s plus the current value of n to help track them later on. With the CSS grid layout we specified above, we have seven columns of seven <section> elements.
const mainElem = document.querySelector("main");
const sectionCount = 49;
let n = 1;
while (n <= sectionCount) {
mainElem.innerHTML += `
<section id="s${n}">
<h2>Section ${n}</h2>
</section>
`;
n++;
}
Next we specify an object called prevState, which allows us to keep track of the previously-selected snap target at any point — its properties store the previous inline and block snap targets' IDs. This is important for figuring out if we need to style the new block target or the new inline target each time an event handler fires.
const prevState = {
snapTargetInline: "s1",
snapTargetBlock: "s1",
};
For example, let's say the scroll container is scrolled so that the ID of the new SnapEvent.snapTargetBlock element has changed (it doesn't equal the ID stored in prevState.snapTargetBlock), but the ID of the new SnapEvent.snapTargetInline element is still the same as the ID stored in prevState.snapTargetInline. This means that we've moved to a new snap target in the block direction, so we should style SnapEvent.snapTargetBlock, but we've not moved to a new snap target in the inline direction, so we shouldn't style SnapEvent.snapTargetInline.
This time around, we'll explain the scrollsnapchange event handler function first. In this function, we:
- Start by making sure that a previously-selected
<section> element snap target (as signified by the presence of the select-section class) has the deselect-section class applied so it shows the deselection animation. If no snap target was previously selected, we apply the select-section class to the first <section> in the DOM so it shows up as selected when the page first loads. - Compare the previously-selected snap target ID to the newly-selected snap target ID, for both the block and inline selections. If they are different, it indicates that the selection has changed, so we apply the
select-section class to the appropriate snap target to visually indicate this. - Update
prevState.snapTargetBlock and prevState.snapTargetInline to be equal to the IDs of the scroll snap targets that were just selected, so that when the event next fires, they will be the previous selections.
mainElem.addEventListener("scrollsnapchange", (event) => {
if (document.querySelector(".select-section")) {
document.querySelector(".select-section").className = "deselect-section";
} else {
document.querySelector("section").className = "select-section";
}
if (!(prevState.snapTargetBlock === event.snapTargetBlock.id)) {
event.snapTargetBlock.className = "select-section";
}
if (!(prevState.snapTargetInline === event.snapTargetInline.id)) {
event.snapTargetInline.className = "select-section";
}
prevState.snapTargetBlock = event.snapTargetBlock.id;
prevState.snapTargetInline = event.snapTargetInline.id;
});
When the scrollsnapchanging event handler function fires, we:
- Remove the
pending class from the element that previously had it applied so that only the current pending target is given the pending class and colored darker gray. - Give the current pending element the
pending class so it turns a darker gray, but only if it has not already got the select-section class applied — we want a previously selected target to keep the purple selection styling until a new target is actually selected. We also include an extra check in the if statements to make sure we style only the inline or block pending snap target, depending on which one has changed. Again, we compare the previous snap target to the current snap target in each case.
mainElem.addEventListener("scrollsnapchanging", (event) => {
const previousPending = document.querySelector(".pending");
if (previousPending) {
previousPending.className = "";
}
if (
!(event.snapTargetBlock.className === "select-section") &&
!(prevState.snapTargetBlock === event.snapTargetBlock.id)
) {
event.snapTargetBlock.className = "pending";
}
if (
!(event.snapTargetInline.className === "select-section") &&
!(prevState.snapTargetInline === event.snapTargetInline.id)
) {
event.snapTargetInline.className = "pending";
}
});