JavaScriptでスクロール連動型アニメーションを自作する方法を解説!

この記事の内容

近年、スクロール連動型アニメーションを取り入れているサイトをよく見かけます。 「タイトルや画像が画面中央に達したタイミングでフェードインする」サイトなどは誰もが一度は見たこがあるのではないでしょうか。

過去にAOSというスクロール連動型アニメーションを簡単に実装できるプラグインを紹介しましたが、スクロール連動型アニメーションのプラグインは、制作物によっては以下のような点で導入しづらかったりもします。

  • アニメーションタイプが限定されている
  • カスタマイズが難しい
  • 不要なコードが含まれている
  • 複数のプラグインを使用する場合、それらのプラグイン同士が干渉し、正常に動作しないことがある

このようなデメリットを回避するには、自作する必要があります。 そこで今回は、jQueryを使用したスクロール連動型アニメーションの実装方法を紹介します。

HTML, CSSを追加するだけアニメーションタイプを増やせたり、どのようなサイトでも使いまわせる半プラグインのようなものになっております。

JavaScript版のコードも記載していますので、そちらもぜひ試してみてください。

単体でフェードインさせる

シンプルなスクロール連動型アニメーションを実装します。 要素が画面下部200pxに達したタイミングで0.6秒かけて、50px下の位置から、フェードアップさせます。

初めにデモをご覧ください。

HTMLを記述する

以下が全体のHTMLコードとなります。

html
<section class="single">
    <div class="wrap">
        <h2>DEMO 1</h2>

        <div class="single-item u-fade-type-up js-scroll-trigger">
            <img src="img/sample.jpg" alt="">
        </div>

        <div class="single-item u-fade-type-up js-scroll-trigger">
            <img src="img/sample.jpg" alt="">
        </div>

        <div class="single-item u-fade-type-up js-scroll-trigger">
            <img src="img/sample.jpg" alt="">
        </div>

        <div class="single-item u-fade-type-up js-scroll-trigger">
            <img src="img/sample.jpg" alt="">
        </div>

        <div class="single-item u-fade-type-up js-scroll-trigger">
            <img src="img/sample.jpg" alt="">
        </div>
    </div>
</section>

それでは解説していきたいと思います。

html
<div class="single-item u-fade-type-up js-scroll-trigger">
    <img src="img/sample.jpg" alt="">
</div>

class="single-item”

レイアウト関連のスタイルを指定するためのクラスになります。 こちらのクラスには、アニメーション関連のCSSは指定しませんのでご注意ください。

class=”u-fade-type-up”

アニメーション時のスタイルや動きを指定するためのクラスとなります。 アニメーション関連のスタイルは全てこちらのクラスに指定します。

レイアウト関連のスタイルとアニメーション関連のスタイル(動きを含む)を分けることで、破綻しづらく、管理しやすいコードとなります。「u-」プレフィックスは、「utility」の「u」になります。

詳しく知りたい方は、CSS設計を学んでみてください!

class=”js-scroll-trigger”

こちらがトリガーとなります。
jQuery側で要素が画面下部200pxに達したかを判定する際に必要となります。

また、「js-」プレフィックスがついているクラスは、jQueryから操作するクラスですので、CSSの指定はしません。

CSSを記述する

全体のCSSコードとなります。

css
.demo01-item{
    margin-top: 80px;
}

.demo01-item:first-of-type{
    margin-top: 0;
}

.u-fade-type-up{
    transform: translateY(50px);
    opacity: 0;
}

.u-fade-type-up.is-active{
    transition: .6s;
    transform: translateY(0);
    opacity: 1;
}

それでは解説していきたいと思います。

スタイル

css
.demo01-item{
    margin-top: 80px;
}

.demo01-item:first-of-type{
    margin-top: 0;
}

画像間の余白を指定しています。

アニメーションスタイル

css
.u-fade-type-up{
    transform: translateY(50px);
    opacity: 0;
}

.u-fade-type-up.is-active{
    transition: .6s;
    transform: translateY(0);
    opacity: 1;
}

上記がアニメーションスタイルになります。

css
.u-fade-type-up{
    transform: translateY(50px);
    opacity: 0;
}

アニメーション前のスタイルになります。

transform: translateY(50px);

要素を50px下の位置に配置しています。

opacity: 0;

要素を透明にし、隠します。

css
.u-fade-type-up.is-active{
    transition: .6s;
    transform: translateY(0);
    opacity: 1;
}

アニメーション後のスタイルになります。 トリガーが画面下部200pxに達したタイミングで、jQueryにてis-activeクラスを付与します。

transition: .6s;

0.6秒かけてアニメーションさせたいので、transition: .6s;を指定します。

transform: translateY(0);

50px下の位置に配置した要素を元の位置(0px)に戻します。

opacity: 1;

透明度0の状態から透明度1の状態に戻します。

jQueryを記述する

全体のjQueryコードとなります。

javascript
$(function () {

    // aimation呼び出し
    if ($('.js-scroll-trigger').length) {
        scrollAnimation();
    }

    // aimation関数
    function scrollAnimation() {
        $(window).scroll(function () {
            $(".js-scroll-trigger").each(function () {
                let position = $(this).offset().top,
                    scroll = $(window).scrollTop(),
                    windowHeight = $(window).height();

                if (scroll > position - windowHeight + 200) {
                    $(this).addClass('is-active');
                }
            });
        });
    }
    $(window).trigger('scroll');

});

処理は関数化しています。 ページ内にjs-scroll-triggerクラスが存在した場合、スクロール連動型アニメーションの処理であるscrollAnimation関数を実行します。

それでは解説していきたいと思います。

関数呼び出し

javascript
$(function () {

    // aimation呼び出し
    if ($('.js-scroll-trigger').length) {
        scrollAnimation();
    }

});

scrollAnimation関数の呼び出しを行っています。

if文の条件$('.js-scroll-trigger').lengthは、ページ内にjs-scroll-triggerクラスが存在した場合「true」となり、関数呼び出しが行われます。反対にページ内にjs-scroll-triggerクラスが存在しなかった場合は、呼び出しは行われません。

scrollAnimation関数は、常に実行していても問題ないのですが、js-scroll-triggerクラスの有無を確認した上で、関数を実行するか否かを判断している理由としては、scrollAnimation関数内にscrollイベントが含まれており、このscrollイベントは比較的負荷の高い処理になります。従って、負荷を最小限に抑えるために、ページ内にスクロール連動アニメーションが存在した場合にのみ、処理を実行しています。

scrollAnimation関数

javascript
$(function () {

    // aimation関数
    function scrollAnimation() {
        $(window).scroll(function () {
            $(".js-scroll-trigger").each(function () {
                let position = $(this).offset().top,
                    scroll = $(window).scrollTop(),
                    windowHeight = $(window).height();

                if (scroll > position - windowHeight + 200) {
                    $(this).addClass('is-active');
                }
            });
        });
    }
    $(window).trigger('scroll');

});
javascript
$(window).scroll(function (){

});

スクロールに連動させるためには、常に処理に必要となる要素の位置を監視、取得しなければなりません。従って、scrollイベントを使用し、スクロール時に処理が行われるようにします。

javascript
$(".js-scroll-trigger").each(function(){

});

eachメソッドを使用し、全トリガー要素(class=”js-scroll-trigger”が指定されている要素)に対して処理を適応させます。

.each()は、指定した要素や配列、オブジェクトなどに対し、ループ処理を行うメソッドとなります。従って、$(".js-scroll-trigger")に対して指定することで、サイト内の全トリガー要素に対して、以下の処理を適応することが可能となります。

javascript
let position = $(this).offset().top,
    scroll = $(window).scrollTop(),
    windowHeight = $(window).height();

ここでは、「トリガーが画面下部200pxに達したか」を判定するために必要な情報を変数に格納しています。

position = $(this).offset().top

.offset().topは、ドキュメント最上部を基準に指定要素の位置のY座標を戻り値として返します。従って、変数positionには各トリガーの垂直方向の位置が格納されます。

scroll = $(window).scrollTop()

scrollTop()メソッドは、垂直方向のスクロール位置を戻り値として返します。従って、$(window)に対して、.scrollTop()を指定することで、ブラウザのスクロール位置を取得することが可能となります。

windowHeight = $(window).height()

ウィンドウの高さを取得し、格納しています。

javascript
// 発火位置を「画面下部から200pxの位置」に設定する場合
if (scroll > position - windowHeight + 200){
    $(this).addClass('is-active');
}

// 発火位置を「画面中央」に設定する場合
if (scroll > position - windowHeight / 2){
    $(this).addClass('is-active');
}

ここでは、「トリガーが画面下部200pxに達したか」を判定し、達した場合にis-activeクラスを指定し、アニメーションを実行しています。

今回のDEMOでは、発火位置を「画面下部200px」に設定しておりますが、実装していると「画面中央」に設定したい場合も出てきます。その際のカスタマイズも簡単ですので、参考にしてみてください。

javascript
$(window).trigger('scroll');

リロード時などに、is-activeクラスが外れてしまうのを防いでいます。

今回の処理では、スクロール時に「処理に必要となる要素の位置情報を常に監視、取得し、その情報に基づいて、画面下部200pxに達したかを判定し、アニメーションを実行している」と説明しました。しかし、仮に要素が画面下部200pxに達していた場合でも、リロードなど、再読み込みをかけてしまうと変数に格納されていた位置情報がリセットされてしまい、指定したはずのクラスが外れてしまうといったことが起こります。1pxでもスクロールし、スクロールイベントを発火させれば、各要素の位置情報が再度、変数に格納され元の状態に戻りますが、これですと見栄えもよくありませんし、想定していた挙動と異なった結果になってしまいます。ですので、JavaScriptファイルが読み込まれた際に意図的にスクロールイベントを発生させ、各変数に再度位置情報を格納し、上記の挙動を回避しています。

trigger()メソッドは、任意のタイミングで指定したイベントを実行することができるjQueryメソッドで、引数にはイベントを指定します。windowに対して、.trigger('scroll')を指定することで実際にはスクロールを行なっていないにも関わらず、スクロールイベントを発生させることが可能になります。

.trigger('scroll')は、スクロール系の処理でよく利用しますので覚えておきましょう!

一斉にフェードインさせる

「一斉にフェードインさせる」スクロール連動型アニメーションを実装します。

DEMO1は、要素単体でのフェードアップでしたが、DEMO2では、ある一つのトリガーが画面下部200pxに達したタイミングで複数の要素を0.4秒おきに一斉にフェードアップさせます。また、フェードアップの挙動に関しましては、DEMO1同様0.6秒かけて、50px下の位置から実行します。

DEMO1の HTML, CSS を変更するだけで簡単に実装できます。

初めにデモをご覧ください。

※DEMO1と異なる箇所のみ解説していきます。

HTMLを記述する

以下が全体のHTMLコードとなります。

html
<section class="multiple">
    <div class="wrap">
        <h2 class="demo-ttl">DEMO 2</h2>

        <div class="multiple-wrap js-scroll-trigger">
            <div class="multiple-item u-fade-type-up">
                <img src="img/sample.jpg" alt="">
            </div>

            <div class="multiple-item u-fade-type-up">
                <img src="img/sample.jpg" alt="">
            </div>

            <div class="multiple-item u-fade-type-up">
                <img src="img/sample.jpg" alt="">
            </div>

            <div class="multiple-item u-fade-type-up">
                <img src="img/sample.jpg" alt="">
            </div>

            <div class="multiple-item u-fade-type-up">
                <img src="img/sample.jpg" alt="">
            </div>

            <div class="multiple-item u-fade-type-up">
                <img src="img/sample.jpg" alt="">
            </div>
        </div>
    </div>
</section>

それでは解説していきたいと思います。

html
<div class="multiple-wrap js-scroll-trigger">
    <div class="multiple-item u-fade-type-up">
        <img src="img/sample.jpg" alt="">
    </div>

    <div class="multiple-item u-fade-type-up">
        <img src="img/sample.jpg" alt="">
    </div>

    <div class="multiple-item u-fade-type-up">
        <img src="img/sample.jpg" alt="">
    </div>

    <div class="multiple-item u-fade-type-up">
        <img src="img/sample.jpg" alt="">
    </div>

    <div class="multiple-item u-fade-type-up">
        <img src="img/sample.jpg" alt="">
    </div>

    <div class="multiple-item u-fade-type-up">
        <img src="img/sample.jpg" alt="">
    </div>
</div>

DEMO2では、トリガーが画面下部200pxに達したタイミングでトリガー内の要素を一斉フェードインさせたいので、トリガーとなるdivに対してjs-scroll-triggerクラスを、フェードイン要素となるdivに対してu-fade-type-upクラスを指定します。

CSSを記述する

以下が全体のCSSコードとなります。

css
.multiple-wrap{
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;
}

.multiple-item{
    width: 49%;
    margin-top: 20px;
}

.multiple-item:nth-child(-n+2){
    margin-top: 0;
}

.u-fade-type-up{
    transform: translateY(50px);
    opacity: 0;
}

.is-active .u-fade-type-up{
    transition: .6s;
    transform: translateY(0);
    opacity: 1;
}

.is-active .u-fade-type-up:nth-child(2){transition-delay: .4s;}
.is-active .u-fade-type-up:nth-child(3){transition-delay: .8s;}
.is-active .u-fade-type-up:nth-child(4){transition-delay: 1.2s;}
.is-active .u-fade-type-up:nth-child(5){transition-delay: 1.6s;}
.is-active .u-fade-type-up:nth-child(6){transition-delay: 2s;}

@media screen and (min-width: 768px) {
    .multiple-item{
        width: 32%;
    }

    .multiple-item:nth-child(-n+3){
        margin-top: 0;
    }
}

それでは解説していきたいと思います。

スタイル

css
.multiple-wrap{
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;
}

.multiple-item{
    width: 49%;
    margin-top: 20px;
}

.multiple-item:nth-child(-n+2){
    margin-top: 0;
}

@media screen and (min-width: 768px) {
    .multiple-item{
        width: 32%;
    }

    .multiple-item:nth-child(-n+3){
        margin-top: 0;
    }
}

SP時は縦並びにし、PC時はflex-boxを利用し、横並べにしています。

アニメーションスタイル

css
.u-fade-type-up{
    transform: translateY(50px);
    opacity: 0;
}

.is-active .u-fade-type-up{
    transition: .6s;
    transform: translateY(0);
    opacity: 1;
}

.is-active .u-fade-type-up:nth-child(2){transition-delay: .4s;}
.is-active .u-fade-type-up:nth-child(3){transition-delay: .8s;}
.is-active .u-fade-type-up:nth-child(4){transition-delay: 1.2s;}
.is-active .u-fade-type-up:nth-child(5){transition-delay: 1.6s;}
.is-active .u-fade-type-up:nth-child(6){transition-delay: 2s;}

上記がアニメーションスタイルになります。

css
.is-active .u-fade-type-up{
    transition: .6s;
    transform: translateY(0);
    opacity: 1;
}

アニメーション後のスタイルになります。

demo1の単体フェードインは、フェードイン要素にis-activeがつきましたが、一斉フェードインでは、フェードイン要素ではなく、親要素のトリガーにis-activeがつきますので、アニメーション後のスタイル指定は.is-active .u-fade-type-upのようになります。

transition-delay: ~;

css
.is-active .u-fade-type-up:nth-child(2){transition-delay: .4s;}
.is-active .u-fade-type-up:nth-child(3){transition-delay: .8s;}
.is-active .u-fade-type-up:nth-child(4){transition-delay: 1.2s;}
.is-active .u-fade-type-up:nth-child(5){transition-delay: 1.6s;}
.is-active .u-fade-type-up:nth-child(6){transition-delay: 2s;}

各要素を0.4秒おきにフェードアップしたいので、transition-delayを利用し、遅延実行しています。1つ目の要素は、トリガーにis-activeがついたと同時に実行され、2つ目の要素は0.4秒後、3つ目の要素は0.8秒後...に実行されます。

ディレイは、jQuery側で指定することも可能ですが、負荷やカスタマイズのしやすさを考慮し、CSS側で指定しています。基本的にスクロール連動は、限られた箇所でしか利用しませんので、CSS側で指定することで管理がしづらくなることはないかと思います。

これでDEMO2の実装が完了となります。

JavaScriptで実装する

jQueryを利用せず、ネイティブのJavaScriptで実装する際は、以下のコードに変更してください。やっていることは、jQuery版と同じです。

コードに関して気になる方は、ご自身で調べてみてください。

javascript
// トリガー取得
const scrollTrigger = document.querySelectorAll('.js-scroll-trigger');

// aimation呼び出し
if (scrollTrigger.length) {
    scrollAnimation(scrollTrigger);
}

// aimation関数
function scrollAnimation(trigger) {
    window.addEventListener('scroll', function () {
        for (var i = 0; i < trigger.length; i++) {
            let position = trigger[i].getBoundingClientRect().top,
                scroll = window.pageYOffset || document.documentElement.scrollTop,
                offset = position + scroll,
                windowHeight = window.innerHeight;

            if (scroll > offset - windowHeight + 200) {
                trigger[i].classList.add('is-active');
            }
        }
    });
}

サンプルデモ

実装頻度の高いスクロール連動アニメーションをいくつか作成しました。 サンプルデモのアニメーションは、HTML, CSSのみ追加したものになります。

まとめ

今回は、jQuery, JavaScriptを使用したスクロール連動型アニメーションの実装法を紹介しました。

アニメーションタイプを増やしたりとカスタマイズもしやすいコードとなっていますので、みなさん是非試してみてください!

Share