LoginSignup
0
0

More than 1 year has passed since last update.

[Vue]lazyload+ローダーの制御

Posted at

はじめに

  • 画像のlazyloadしたかった時につまづいたので覚え書き
  • 同じ悩みを持つ人がいらっしゃったら1mmでも参考になれば幸いです。

環境

  • OS : Windows Home 10
  • ブラウザ : Chrome
  • 言語 : Vue(2.x)

やりたいこと

以下2点がやりたいこと

  • lazyloadを使った画像の表示
  • lazyload中によくあるローダー(クルクル回るやつ)を表示させたい

状況

  • S3などのクラウドにある画像データをダウンロードストリーミングしているとブラウザの表示までにかなり時間を要する
  • これを「必要な分だけ表示する」ためにlazyloadを導入した

※「lazyloadとは?」という状態から始めたので、参考にさせていただいた記事を引用します。

  • lazyload

https://paralux.co.jp/blog/358

イメージ画像があってわかりやすかったです。

※ついでに「こんなやつね」くらいのイメージを捉えるためにローダーの参考も引用。

  • ローダー

https://projects.lukehaas.me/css-loaders/

やったこと

画像のローダー表示自体よくある処理なので

「JavaScript lazyload ローダー」

「Vue lazyload ローダー」

等の検索キーワードで検索をしてみた

問題点

検索結果に出てこない、、、意外だった。

「lazyloadの導入方法」や「lazyload中に固定画像を表示する方法」はあるが、「+ローダーの表示」まで至る記事がなかった

対策

ということで、自作を決意

表示するモノ

表示するモノは以下の通り

  • 本画像…lazyloadで読み込みたい画像
  • ローダー…よくあるクルクル回るアレ
  • ダミー画像…lazyload中に読み込みたい画像。今回はローダーを表示するので透明画像を使用
  • エラー画像…読み込み失敗時の画像。よくあるNO IMAGE画像を使用

方針

「やりたいこと」に沿って該当ページへの初回アクセス時からの流れを整理すると

  • 本画像読み込み中…ローダーを表示(同時にダミー画像を表示)
  • 本画像読み込み完了…ローダー非表示→本画像表示
  • 本画像読み込み失敗…ローダー非表示→エラー画像表示

準備

lazyloadを実装するには自分が調べた限り、3つの方法があった(他にもあったらすいません)

1, JSライブラリlazyload による制御

2, HTML属性loadingによる制御

3, Vue用ライブラリvue-lazyload による制御

今回はVueを使っていることもあり、vue-lazyload を使用

実は、、、

実は当初、 lazyloadを最初導入していたんです。

しかし、Vue内で生成する動的コンポーネントとの相性が悪いとの理由で 3, vue-lazyload の使用に至りました。

※尚、1, lazyload でも実装は可能ですが、lazyloadが必要なコンポーネントに都度updatedメソッドの中でlazyload()を読み込みしなくてはいけない点で冗長だったので却下しました。

実装

以下流れで実装します。


1, https://github.com/hilongjw/vue-lazyload のインストール

2, https://github.com/hilongjw/vue-lazyload の設定

3, テンプレートの作成

4, ローダーの作成

5, ローダーの制御


ただ、1 ~ 3はvue-lazyloadの基本的な使い方なのでこれができた前提で話を進めます。

※1 ~ 3はこちらを参考にさせていただきました。

以下が1~3が既に終わった状態です。

前提としてVue-CLIをインストールして新プロジェクトを作成したタイミングから始めています。


app.js
import Vue from 'vue';
import App from './App.vue';
// 追加
import VueLazyload from 'vue-lazyload';
// ※追加(なくてもOK)
import * as image from './config/image';
// 追加
Vue.use(VueLazyload);

// ※追加(なくてもOK)
const dummyURL = image.DUMMY_URL;
const noImageUrl= image.NO_IMAGE_URL;

// 追加
Vue.use(VueLazyload, {
  loading: dummyURL, // ※オプション
  error: noImageUrl, // ※オプション
});

// オプションを書かないのであれば以下
Vue.use(VueLazyload);

new Vue({
  el: 'body',
  components: {
    App
  }
});
  • vue-lazyloadの設定箇所
  • lazyload中とエラー発生時の画像をここで設定できるのは結構うれしい
    • 各コンポーネントにdataプロパティに各々設定しなくていい
  • errorやloading以外にもオプションがあったり、オプションを書かずに各コンポーネントで個別でオプションをかけたり、、、詳しくはドキュメント参照
config/image.js
export const DUMMY_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNkYAAAAAYAAjCB0C8AAAAASUVORK5CYII=";
export const NO_IMAGE_URL = "https://placehold.jp/30/cccccc/ffffff/300x150.png?text=no-image";
  • 表示するダミー画像とエラー画像のパスを指定
  • DUMMY_URLNO_IMAGE_URLのパスは各々以下から取得しています。

https://png-pixel.com/

https://placehold.jp/

image-list.component.vue
<template>
  <div>
    <ul>
      <li
        v-for="item in items"
        :key="item"
      >
        <img
          v-lazy="item.imageUrl"
        >
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    images: {
      type: Object,
      default: {},
    },
  },
}
</script>
  • 画像の表示箇所
  • itemsというオブジェクトからitem単位でループをまわし、itemから表示したいimageUrlを取得
  • itemsは親コンポーネントからpropsで受け取っている、という前提
  • v-lazyを書くだけでロード中もエラー時も本画像も表示できてしまう
    • 実際には内部的にloadイベント、errorイベントを検知しloading属性の要素をVueで切り替えている制御方法

ここまでで1 ~ 3の実装は終わったのでローダーについて実装します。

4, ローダーの作成

  • これは先ほど掲載した参考サイトのソースコードを頂戴してきます。
    • 今回はサイトの左上から右に2番目のローダーを表示します。
loader-component.vue
<template>
  <div
    class="loader"
  />
</template>

<style scoped>
.loader,
.loader:before,
.loader:after {
  border-radius: 50%;
}
.loader {
  color: #ffffff;
  font-size: 11px;
  text-indent: -99999em;
  margin: 55px auto;
  position: relative;
  width: 10em;
  height: 10em;
  box-shadow: inset 0 0 0 1em;
  -webkit-transform: translateZ(0);
  -ms-transform: translateZ(0);
  transform: translateZ(0);
}
.loader:before,
.loader:after {
  position: absolute;
  content: '';
}
.loader:before {
  width: 5.2em;
  height: 10.2em;
  background: #0dc5c1;
  border-radius: 10.2em 0 0 10.2em;
  top: -0.1em;
  left: -0.1em;
  -webkit-transform-origin: 5.1em 5.1em;
  transform-origin: 5.1em 5.1em;
  -webkit-animation: load2 2s infinite ease 1.5s;
  animation: load2 2s infinite ease 1.5s;
}
.loader:after {
  width: 5.2em;
  height: 10.2em;
  background: #0dc5c1;
  border-radius: 0 10.2em 10.2em 0;
  top: -0.1em;
  left: 4.9em;
  -webkit-transform-origin: 0.1em 5.1em;
  transform-origin: 0.1em 5.1em;
  -webkit-animation: load2 2s infinite ease;
  animation: load2 2s infinite ease;
}
@-webkit-keyframes load2 {
  0% {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}
@keyframes load2 {
  0% {
    -webkit-transform: rotate(0deg);
    transform: rotate(0deg);
  }
  100% {
    -webkit-transform: rotate(360deg);
    transform: rotate(360deg);
  }
}
</style>

同時に、ローダーを表示したいimage-list.component.vueに取り込んでおく

image-list.component.vue
...
<script>
export default {
    ...
  // 追加
  components: {
        loader-component
  },
}
</script>

5, ローダーの制御

方法としては専用プロパティを設定しローダーを表示/非表示をtrue / falseで制御する形をとりました。要は

  • loading = true でローダー表示
  • loading = false でローダー非表示

という整理

なので、まずはdataプロパティにローダーの制御用プロパティloadingを設定

image-list.component.vue
...
<script>
export default {
    ...
    // 追加
    data: () {
        return {
            loading: false,
        }
    } 
}
</script>

また、loadingの値によって動的にローダーを表示/非表示を制御するため、ローダーにloadingを紐づける

image-list.component.vue
        <img
          v-lazy="item.imageUrl"
        >
        // 追加
        <loader-component
            v-if="loading"
        >
        </loader-component>

そして、まずは表示時の制御。

これはページ全体の読み込み時でいいのでmounted()を追加し、ここでローダーを表示させる

image-list.component.vue
<script>
export default {
    ...
    // 追加
    mounted: () {
        this.loading = true;
    }
    ...
}
</script>

そして非表示時の制御。

これはVueのloadイベントを発火させて制御します。

ちょっと困ったこと

ただ、こっから僕が困ったことです。

ローダー表示と同じように以下のように@loadを追加してonLoadメソッドを呼び出すようにして

image-list.component.vue
        <img
          v-lazy="item.imageUrl"
          // 追加
          @load="onLoad"
        >
        <loader-component
            v-if="loading"
        >
        </loader-component>

その際に呼び出すメソッドonLoadメソッドでローダーを非表示にすると、

image-list.component.vue
<script>
export default {
    ...
    // 追加
    methods: {
        onLoad(event) {
            this.loading = false;
        },
    }
    ...
}
</script>

ダミー画像表示のタイミングで@loadイベントが発火してローダーが非表示になるという挙動をしました。

原因

原因はonLoadメソッド内でデバックしたらわかったんですが、lazyloadのデフォルトの挙動として@loadイベントは、「ダミー画像の読み込み完了時」と「本画像の読み込み完了時」の合計2回呼び出されることでした。

なので、ブラウザの動きとしては画像の読み込み中に

ローダーを表示すぐにダミー画像を読み込みローダーが非表示
(ローダーが一瞬表示→透明画像の表示(何も表示されない)、という状態)

という状態になった、という状況です。

対策

@loadイベントは合計2回呼び出されますが、幸いにも各々呼び出されるDOMがダミー画像用、本画像用で分かれていたことから以下のようにしました。

image-list.component.vue
<script>
// 追加
import * as image from './config/image';

export default {
    ...
    methods: {
        onLoad(event) {
            // 追加
            if (event.target.src !== image.DUMMY_URL) {
                this.loading = false;
            }
        },
    }
    ...
}
</script>

要は

  • 1回目の@loadイベント:event.target.srcはダミー画像用のパス
  • 2回目の@loadイベント:event.target.srcは本画像画像用のパス

となり、ダミー画像読み込み完了時のイベントはsrc属性がDUMMY_URL(config/image.jsで設定済)であるのでこれをスルーし本画像読み込み完了時にloadingをfalseにすることでローダーを非表示にする、という制御が可能になりました。

結果

これで当初の予定である、


  • 本画像読み込み中…ローダーを表示(内部的にはダミー画像を表示)
  • 本画像読み込み完了…ローダー非表示→本画像表示
  • 本画像読み込み失敗…ローダー非表示→エラー画像表示

これを実現出来ました。

補足

いくつか補足です。

エラー時の挙動について

謎な部分がありました。

本画像が読み込めなくなったエラー発生時、以下のnoImageUrlを表示する設定にしており、ここまでの実装でエラー画像の表示は機能はしていた。

app.js
...
Vue.use(VueLazyload, {
  loading: dummyURL,
  error: noImageUrl, // エラー画像
});
...

なので、@errorでエラーイベントの取得ができるだろうと思ったので@erroronErrorメソッドを呼び出すようにして

image-list.component.vue
        <img
          v-lazy="item.imageUrl"
          @load="onLoad"
          // 追加
          @error="onError"
        >
        <loader-component
            v-if="loading"
        >
        </loader-component>

onErrorメソッドの中でconsole.logを構えていたが、、

image-list.component.vue
<script>
export default {
    ...
    methods: {
        // 追加
        onError(event) {
            console.log(evevnt.target.src);
        },
    }
    ...
}
</script>

結果として、onErrorメソッドに処理が入らず、正常処理と同様にonLoadメソッドが2回呼ばれました。

なお、冒頭に話したJSライブラリ lazyload の使用時はちゃんと@errorでイベントが取得できたので、これはvue-lazyloadの仕様かな、と思います。

素直に@errorイベントを読んでくれないのはちょっと扱いにくい点かな、、、と思いました。

lazyloadでsrc属性を書かなければ挙動は問題ない

JSライブラリ lazyload 使用時に、imgタグのsrc属性を書かずに、data-src属性のみ書いてもブラウザの挙動上は問題ないので、ここで妥協しようとも考えました。

ただ、この記事によると

lazyload.jsにはダミー画像を自動的に生成するプログラムが組み込まれている

とあり、srcがなくとも実装できますが

imgタグにsrc属性を指定しないとW3Cのチェックに引っかかってしまう

とのことでした。

実際に「W3Cのチェックに引っかかってしまう」とはなんなのか検索しましたが、特に関連記事が見つからずでした。

とはいえ、不確定要素のあるモノを実装するのは怖いのと、他に選択肢があったので、今回は「srcを実装しない」という実装方法は却下しました。

おわりに

「lazyloadとは何ぞ?」から始まりましたが、まだ慣れていないVueの処理の流れを理解し、またイベント内での処理を書くことで融通の利く処理が書けるんだな、と色々と個人的に学びが多い作業でした。

正直、記事を書いていて「そこまで大した処理ではないからわざわざ記事になっていないだけかな。。。」と思いましたが、どうせならと思い投稿させて頂きました。

「このほうがいいんじゃないか」等々、忌憚のないご意見お待ちしております。

読んでいただいた方へ、最後までお読みいただきありがとうございました。

0
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
0
0