1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Freshでスクロールすると浮き上がってくるようなアニメーションを実装してみる

Last updated at Posted at 2025-12-23

はじめに

みなさん、普段ウェブサイトでこのようなアニメーションを見たことがありませんか?
スクロールすると要素が浮かび上がってくるようなアニメーション
今回はこのアニメーションをWeb APIのIntersection Observer APIを活用することで実装していきます!

islandで実装していく

クライアント側でのインタラクションを可能にするために、islandで作成していきます。
今回作っていくScrollRevealは次のような利用を想定しています。

<ScrollReveal>
    <div>
        {/* いろいろ */}
    </div>
</ScrollReveal>

はい、アニメーションさせたい要素をScrollRevealで囲むだけです。

下準備

まずはJSX.HTMLAttributes<HTMLDivElement>をつかって、<div>で使える属性と、ScrollReveal内で利用するデータを受け取るための型Propsを作りましょう。

import type { JSX } from "preact"; 

type Props = JSX.HTMLAttributes<HTMLDivElement> & {
    threshold?: number;			// 要素がどの程度見えてから表示するか
    once?: boolean;				// 一度だけ実行するか
};

use○○を使っていく

どの要素に対しての処理なのかをuseRefで、
要素が見えたかどうかの状態にuseStateを使います。
また、IntersectionObserverを使うために、useEffectを使います。

const ref = useRef<HTMLDivElement>(null);
const [isVisible, setVisible] = useState(false);

useEffect(()=>{
    const element = ref.current;
    if(!element) return;
    
    const observer = new IntersectionObserver(([entry])=>{
        // TODO
    }, { threshold });

    // 監視の開始
    observer.observe(element);
});

全体像

islands/ScrollReveal.tsx
export default function ScrollReveal({
    children,
    threshold = 0.5,
    once = true,
    ...props
}: Props){
    const ref = useRef<HTMLDivElement>(null);
    const [isVisible, setVisible] = useState(false);

    useEffect(()=>{
        const element = ref.current;
        if(!element) return;

        const observer = new IntersectionObserver(([entry])=>{
            if(entry.isIntersecting){
                // 画面に入ったらvisible = trueにする
                setVisible(true);

                // 一度だけ実行なら監視を終了
                if(once){
                    observer.unobserve(element);
                }
            }else if(!once){
                setVisible(false);
            }
        }, { threshold });

        // 監視の開始
        observer.observe(element);
    });

    // isVisibleの時、アニメーションを適用
    const activeClass = isVisible
        ? `opacity-100 translate-y-0`
        : `opacity-0 translate-y-10`;

    return (
        <div
            ref={ref}
            className={`transition-all duration-750 ease-out ${activeClass}`}
            {...props}
        >
            {children}
        </div>
    );
}

実際に使ってみる

自分の使いたいところでimportして、適用したい要素を<ScrollReveal></ScrollReveal>で囲います。
thresholdonceはデフォルトでそれぞれ、0.5trueが入っているので、特に変更する必要がない場合は書かなくても大丈夫です。

<ScrollReveal>
    <div>
        {/* いろいろ */}
    </div>
</ScrollReveal>

thresholdonceを変更する場合は

<ScrollReveal threshold={0} once={false}>
    <div>
        {/* いろいろ */}
    </div>
</ScrollReveal>

などとしてください!

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?