【Chrome129】ScrollSnapイベントがやってくる!
はじめに
こんにちは!😄
Chrome129より、JavaScriptのScrollSnapに関するイベントを使用できるようになりますね!今回はこれらのイベントについてご紹介していきたいと思います。
ScrollSnapとは
そもそも「ScrollSnapってなんやねん。」という方もいるかと思います。百聞は一見に如かずということで以下のような操作のことを指します。
cssプロパティscroll-snap-type
を指定することでScrollSnapが可能になります。上記の例では以下を適用させています。(詳しくはこちら)
scroll-snap-type: x mandatory;
スマホやタブレットの台頭に伴って、タッチ操作でスワイプさせる場面が増えてきました。ScrollSnapを導入することでUX向上につながります。
そして、Chrome129からScrollSnapが完了したときに発生するscrollSnapChange
イベントと、ScrollSnap中に発生するscrollSnapChanging
イベントを使用することができます。今まではこれらのイベントがなかったため、Intersection Observer APIやscrollend
イベントを使用してスナップ状態を検知していました。しかし、これだとスナップされた要素の特定が限定的で、正確にスナップ状態を検知することが難しい状態でした。
scrollSnapChange
とscrollSnapChanging
の登場により、適切なタイミングでスナップ状態を検知し、適切な操作を行うことが可能になります。それでは各イベントについて見ていきましょう!
scrollSnapChange
イベント
scrollSnapChange
イベントは、スクロールが止まった後、かつscrollend
より前に発生するイベントになります。実際に試してみましょう。
import { useEffect, useRef } from "react";
export const Index = () => {
const scrollerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const scroller = scrollerRef.current;
const handleScrollSnapChange = () => {
console.log("scrollsnapchange!!");
};
const handleScrollEnd = () => {
console.log("scrollend!!");
};
scroller?.addEventListener("scrollsnapchange", handleScrollSnapChange);
scroller?.addEventListener("scrollend", handleScrollEnd);
return () => {
scroller?.removeEventListener("scrollsnapchange", handleScrollSnapChange);
scroller?.removeEventListener("scrollend", handleScrollEnd);
};
}, []);
return (
<div ref={scrollerRef}>
<div>HTML</div>
<div>CSS</div>
<div>JavaScript</div>
<div>Go</div>
<div>Rust</div>
</div>
);
};
scrollsnapchange
イベント後にscrollend
イベントが発生していることが分かりましたね!またユーザーがジェスチャーし続けている場合(スナップし続けている場合)は、scrollsnapchange
イベントは発生しないことも分かります。このようにscrollsnapchange
イベントを使用することによってスクロール/ユーザージェスチャーが完了していることを検知できます。
scrollSnapChanging
イベント
scrollSnapChanging
イベントは、スクロール操作によって新しいスタップターゲットが作成された場合発生するイベントです。実際に試してみましょう。
import { useEffect, useRef } from "react";
export const Index = () => {
const scrollerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const scroller = scrollerRef.current;
const handleScrollSnapChanging = (e) => {
console.log("scrollsnapchanging to", e.snapTargetInline.innerHTML);
};
scroller?.addEventListener("scrollsnapchanging", handleScrollSnapChanging);
return () => {
scroller?.removeEventListener(
"scrollsnapchanging",
handleScrollSnapChanging
);
};
}, []);
return (
<div ref={scrollerRef}>
{/** 省略 */}
</div>
);
};
見ていただいて分かる通り、スナップターゲットが作成されるたびにscrollSnapChanging
イベントが発生します。またユーザーがジェスチャーし続けている間でも、スナップターゲットが作成されるとイベントが発生しています。高速スクロールをした場合は、スクロールが完了するまでの間の要素(☝️で言うと「CSS・JavaScript・Go」)ではscrollSnapChanging
イベントは発生せず、最後の要素(☝️で言うと「Rust」)のみで発生していることが分かりますね!
実際に使ってみる
scrollSnapChange
とscrollSnapChanging
イベントを使って、UIを少しリッチにしてみました。
import { useEffect, useRef } from "react";
import "./style.css";
export const Index = () => {
const scrollerRef = useRef<HTMLDivElement>(null);
useEffect(() => {
const scroller = scrollerRef.current;
const handleScrollSnapChange = (e) => {
scroller?.querySelector(":scope .snapped")?.classList.remove("snapped");
scroller
?.querySelector(":scope .snapTarget")
?.classList.remove("snapTarget");
e.snapTargetInline.classList.add("snapped");
};
const handleScrollSnapChanging = (e) => {
scroller?.querySelector(":scope .snapped")?.classList.remove("snapped");
scroller
?.querySelector(":scope .snapTarget")
?.classList.remove("snapTarget");
e.snapTargetInline.classList.add("snapTarget");
};
scroller?.addEventListener("scrollsnapchange", handleScrollSnapChange);
scroller?.addEventListener("scrollsnapchanging", handleScrollSnapChanging);
return () => {
scroller?.removeEventListener("scrollsnapchange", handleScrollSnapChange);
scroller?.removeEventListener(
"scrollsnapchanging",
handleScrollSnapChanging
);
};
}, []);
return (
<div ref={scrollerRef}>
<div className="section__item">HTML</div>
<div className="section__item">CSS</div>
<div className="section__item">JavaScript</div>
<div className="section__item">Go</div>
<div className="section__item">Rust</div>
</div>
);
};
.section__item {
/** 省略 */
&:not(.snapped) {
opacity: 0.25;
}
&.snapTarget {
opacity: 0.6;
}
}
スクロール中はスナップターゲットにはopacity: 0.6
を適用し、それ以外の要素はopacity: 0.25
を適用するようにしています。またscrollsnapchange
イベントでスクロール完了を検知するとスナップターゲットにはopacity
を適用しないようにしています。
scrollSnapChange
とscrollSnapChanging
イベントが発生するタイミングでopacity
をかけているだけですが、今どれがスナップターゲットになっているか、スクロールが完了しているのかどうかが一目でわかるようになりましたね!
最後に
scrollSnapChange
とscrollSnapChanging
イベントの登場により、スナップをトリガーとするアニメーションを作成することが簡単になります。以下のようなデモがいくつか紹介されており、ワクワクするようなUIの作成が可能になります!ぜひ一度触ってみてはいかがでしょうか!!
最後までお読みいただき、ありがとうございました。
Discussion