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";
}
});