すぐできる!Vueでlazy loading を実装する

さてさて、このブログではLaravelの記事と並んでVue.jsに関する記事も多く公開しています。

Vueは私が使ったJSフレームワークの中で一番すぐ使える(=学習コストが低い)と考えていて、JavaScriptの知識があればおそらくVueは2、3日もあれば基本的な動作はマスターできるんじゃないかと思っています。

そのため、他の開発者さんと共同で作業をする際でもできるだけVueを使うように提案をしています。(もちろんReactも素晴らしいんですが、学習コストはVueよりも高いのでReact経験者でないと比較的引き継ぎしにくいと考えています。あとはいちいちビルドするのがどうしても好きじゃないんですー😫)

そして、今回はそんなVueで1つやってみようと思っていた機能を思い出しました。

それが、

Lazy Loading(れいじーろーでぃんぐ)

です。

Lazy Loadingというのは、元々インターネット回線が遅くてもストレスを感じないようにページ読み込みするために考えられたテクニックで、例えば以下の画像を見てください。

これは、元画像は50 x 50 pxの小さなサムネイルですが300 x 300 px に引き伸ばして表示したものです。(ですので、ぼやけています)

このようにページが読み込まれたときは、小さな画像を読み込んで表示しておき、後で少しずつオリジナルの(この場合は300 x 300 pxの)大きな画像を読み込むという機能が「Lazy Loading(遅延読み込み)」と呼ばれるものになります。

つまり、Lazy Loadingを使えば、もしページ内に大きな画像があったとしても画像が必要なときに読み込まれるのでページ表示は早くなるというわけですね。

ということで!

今回はVueでこのLazy Loadingを実装してみたいと思います。

ぜひ皆さんのお役に立てると嬉しいです😊✨

開発環境: Vue 2.6、ES6

やりたいこと

今回はVueの持つ機能「カスタム・ディレクティブ」をつくって実装していきます。

ディレクティブを使った例はこちらです。

<img
    src="(サムネイルのURL)"
    v-lazy-loading="'(オリジナルのURL)'">

そして、オリジナルのURLがロードされるのは、「表示中のウィンドウの中に画像が入ったとき」とします。

つまり、このような状態です。

また、せっかく独自のディレクティブをつくるのでLazy Loadingが完了したらlazy-loadという独自イベントも実行できるようにしていきます。

では実際にやっていきましょう!

ちなみに今回は「子供向け雑誌の付録」のように基本のコードに番号がふってあって、それにコードをはめ込んでいけば完成するという初の試みで話を進めていきたいと思います。

ぜひ楽しんでやってみてください😊✨

今回使う画像について

今回使う画像はURLを指定するだけで好きなサイズの画像が使えるCDNplaceholder.comを使います。例えば、500 x 300 の画像がほしければ、

<img src="https://via.placeholder.com/500x300">

とするだけでOKです。

実際の表示はこのようになります。

基本のHTMLコード

では、まず初めに基本となるHTMLコードです。
この中にふってある番号にコードを追加しながら完成させていきます。

<html>
<head>

    <!-- ① 画像のスタイルシートをつくる -->

</head>
<body>
    <div id="app">
        <div v-for="i in 100">
            <!-- ⑤ 画像タグをつくる -->
        </div>
    </div>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.6.0"></script>
    <script>

        // ④ Vueのミックスインで「v-lazy-loading」が使う関数をつくる

        // ③ Vueのカスタム・ディレクティブ「v-lazy-loading」をつくる

        // ② Vueの基本形をつくる

    </script>
</body>
</html>

この基本コードをlazy_loading.htmlのような名前でファイル保存しておくとブログを読み進めながらコードを追加していくことができますよ!

画像のスタイルシートをつくる・・・①番

表示する画像のサイズをスタイルシートで指定しておきましょう。こうすることで、先に読み込まれる画像は小さいですがサイズを固定させることができます。(つまり、画像はぼやけます)

<style>

    img {

        width: 300px;
        height: 300px;

    }

</style>

Vueの基本形をつくる・・・②

続いては、Vueの基本形です。
この中には、さらに別の作業⑥がありますが、今はこのままコードを追加してください。

new Vue({
    el: '#app',

    // ⑥ 独自イベントのメソッドをつくる

});

なお、#app<div id="app">〜</div>のことを指しています。

Vueのカスタム・ディレクティブ「v-lazy-loading」をつくる・・・③

今回のテーマはカスタム・ディレクティブですが、実はLazy Loadingの性質上メインのコードはもう一つのミックスインの方に多くはいっているためコード量は少なめです。

Vue.directive('lazy-loading', {
    bind(el, binding) {

        el.setAttribute('original-src', binding.value)

    },
    inserted(el) {

        el.className += ' waiting-load';

    }
});

なお、bind()v-lazyloadingで指定するオリジナルの大きな画像のURLを一旦original-srcというプロパティに保持しています。

<img
    src="(先に表示する小さな画像のURL)"
    v-lazy-loading="(オリジナル画像のURL)">

また、inserted()では、あとでLazy Loadingすべき画像が判別できるよう「waiting-load」というクラス名を追加しています。

Vueのミックスインで「v-lazy-loading」が使う関数をつくる・・・④

ここが一番コード量が多いですが、少し複雑なのはたった1つのメソッドだけです。

※ なお、ミックスインはVueの基本形と合体することになります。(使い回しができるように別コードに分けました😊)

Vue.mixin({
    methods: {
        lazyLoading() {

            // ウィンドウの表示位置
            const windowTop = window.scrollY;
            const windowBottom = windowTop + window.innerHeight;

            // Lazy Loadingすべき画像を全て取得
            const images = document.querySelectorAll('.waiting-load');

            [].forEach.call(images, el => {

                // 画像の位置
                const offsetTop = el.offsetTop;
                const offsetBottom = offsetTop + el.offsetHeight;

                if(windowTop < offsetTop && windowBottom > offsetBottom) {

                    // 画像をオリジナルのURLに変更し、不要なデータを削除
                    const originalSrc = el.getAttribute('original-src');
                    el.setAttribute('src', originalSrc);
                    el.removeAttribute('original-src');
                    el.className = el.className.replace(' waiting-load', '');

                    // lazy-load イベントを実行
                    const event = new Event('lazy-load');
                    el.dispatchEvent(event);

                }

            });

        }
    },
    mounted() {

        this.lazyLoading();
        window.onscroll = this.lazyLoading;

    }
});

まず、mounted()はページが読み込まれたらすぐに実行されるメソッドですが、この中ではlazyLoading()というメソッドをすぐに実行し、さらにスクロールしたときにも同じメソッドを実行するようイベントをつけています。

※ 一度すぐに実行しているのは、ページが表示された時点で画像がウィンドウ内に入っていればオリジナル画像を読み込ませるためです。

では、続いてlazyLoading()です。

この中ではまず現在のスクロール位置を取得しています。(この数値を見れば、ウィンドウの上と下の位置、つまり現在ブラウザで表示されている範囲がわかようになります)

次に、document.querySelectorAll()を使って「Lazy Loadingを実行するべき全ての画像」を取得し、それらを[].forEach.call()でループさせています。

そしてループの中では、1つずつ画像のエレメントが取得できるので、その画像の位置と先ほどのウィンドウ位置を比較しウィンドウ内に画像が入っているかをチェックしています。

もしウィンドウ内に入っているようなら、一時的に保管したオリジナル画像のURLをoriginal-srcから取得し、実際に表示されるsrcとして使います。

なお、original-srcwaiting-loadはもう不要なのでここで削除しておきます。(というか、クラス名waiting-loadは削除しないと何度も何度も処理が実行されるので削除しておくべきです)

そして、最後にLazy Loadingされたときに実行されるイベントを起動します。イベント名はどんなものでもいいのでお好みで変更することができます。

画像タグをつくる・・・⑤

さぁ、完成が近づいてきました。
実際に画像タグを以下のようにセットしてください。

<img
    src="https://via.placeholder.com/50?text=50x50"
    v-lazy-loading="'https://via.placeholder.com/300?text=300x300'"
    @lazy-load="onLazyLoad">
<br><br>

すでに説明しましたが、プロパティの意味は以下のとおりです。

  • src ・・・ 先に表示する小さな画像
  • v-lazy-loading ・・・ カスタム・ディレクティブ。セットする値はオリジナル画像のURL ※注意
  • @lazy-load ・・・ オリジナル画像が読み込まれたら実行されるカスタム・イベント。次の項目で作るonLazyLoad()が実行される。

※注意

v-lazy-loading内ではURLをシングルクォート'で囲んでいますが、これはカスタム・ディレクティブにはVueの変数も指定できるからです。

つまり、Vueの変数にオリジナル画像のURLを入れておけば・・・

new Vue({
    el: '#app',
    data: {
        originalUrl: 'https://via.placeholder.com/300?text=300x300'
    }
});

次のように変数の値を参照することができます。(この場合はシングルクォートはいりません)

<img
    src="https://via.placeholder.com/50?text=50x50"
    v-lazy-loading="originalUrl">

独自イベントのメソッドをつくる・・・⑥

では、最後に「Vueの基本形をつくる」で追加した⑥番目の作業、独自イベントで指定したメソッドonLazyLoadを追加します。

methods: {
    onLazyLoad(e) {

        // 独自イベント「lazy-load」
        e.target.style.border = '5px solid red';

    }
}

この中では実行結果が分かりやすいように、画像に赤い枠線をつけるようにしています。

テストしてみる

では、実際に今回のコードをブラウザで表示してみましょう。

まずはページを表示した直後です。

うまくウィンドウ内の画像は300 x 300の画像が読み込まれて赤枠が表示されていますが、まだウィンドウ内に入りきっていない画像は50 x 50のままです。

では、このまま下方向へスクロールをしていきます。

はい!こちらもうまく画像がロードされ赤枠もつきました。

お疲れ様でした😊✨

おわりに

ということで、今回はいつもと少し違って子供雑誌の付録のような形式で記事をお届けしました。

もちろんVueを学習している人全てにお届けしたつもりですが、もし小中学生の皆さんが自由研究的な感じて使ってくれたら嬉しいですね。

(そのため、画像を用意しなくてもいいようにplaceholder.comなどのCDNを使ってコードを書きました😊)

ぜひあまり実際にプログラムをやったことがない方も、まずは遊び感覚でチャレンジしてみてくださいね。

ではでは〜!

この記事が役立ちましたらシェアお願いします😊✨