無限スクロールの実装(Laravel + Vue + axios)

無限スクロール

無限スクロールとはスクロールすると次の記事やコンテンツが表示される機能です。
制作物にあるTypingGameのトップページが無限スクロールを実装しています。実際の挙動はそちらをご覧ください。


axiosで取ってくるのでapi.phpに以下のルーティングを設定します。

Route::get('/items', 'Api\ItemsController@getItems')->name('api.get');

次にコントローラーを設定します。
Itemモデルはあらかじめ作っておいてください。

use App\Models\Item;

public function getItems()
    {
        $items = Item::latest()->paginate(10); //1ページあたり10件表示を降順でとってくる指定
        return ['items' => $items]; //returnする
    }

以上で、Laravel側の設定は完了です。
次にVue側の設定を行います(あらかじめLaravelでVueが使えるように設定しておく)。

Vueのコンポーネントが使えるようにbladeを設定します。

app/Http/Controllers/Api/ItemsController.php
@extends('layouts.app')

@section('content')
    <top-page></top-page>
@endsection
<template>
  <div class="content">
    <div class="items">
      <div v-for="(item, itemIndex) in items" :key="itemIndex">
        <p>{{item.name}}</p>
      </div>
    </div>
    <!-- ローディング -->
    <div class="loading-animation" v-if="itemLoading">
      読み込み中です。
    </div>
  </div>
</template>

<script>
  export default {
    data: () => ({
      itemLoading: false,
      load: true,
      page: 1,
      items: [],
    }),

    mounted(){
      this.clearVar()
      window.addEventListener('scroll', () => {
        let bottomOfWindow = document.documentElement.scrollTop + window.innerHeight == document.documentElement.offsetHeight;
        if (bottomOfWindow) this.getItems();
      };
      this.getItems()
    },

    methods: {
      clearVar() {
        this.itemLoading = false
        this.load = true
        this.page = 1
        this.items = []
      },
      async getItems() {
        if (this.load) { //全体の読み込み
          if (!this.itemLoading) { //読み込み中は読み込めないようにする
            this.itemLoading = true
            try {
              const response = await axios.get('/api/items?page=' + this.page)
              if (response.data.items.last_page == this.page) this.load = false
              if (response.data.items.data) {
                await response.data.items.data.forEach((n,i) => {
                  this.items.push(n)
                })
              }
              this.page += 1
            } catch (e) {
              console.log(e.response)
              this.load = false
              this.itemLoading = false
            } finally {
              this.itemLoading = false
            }
          }
        }
      },
    }
  }
</script>

ただし、このままだとscrollイベントがスクロールされる度に発火し、負担がかかるので
lodashを用いて制御します。

$ npm install --save lodash
window._ = require('lodash');

TopPage.vueのmounted()の部分を変更します。

mounted() {
      this.clearVar();
      window.addEventListener('scroll', _.throttle(() => {
            let bottomOfWindow = document.documentElement.scrollTop + window.innerHeight == document.documentElement.offsetHeight;
            if (bottomOfWindow) this.getItems();
       }, 200, { trailing: true, leading: true }));
       this.getItems();
},

lodashのthrottleのオプションについては以下が詳しいです。
https://www.webprofessional.jp/throttle-scroll-events/
https://liginc.co.jp/371432