LoginSignup
8
9

More than 3 years have passed since last update.

InfiniteLoading 無限スクロール vue.js

Last updated at Posted at 2019-01-23

vue.jsすげーな。
中国でめっちゃ発達しているし、コードもきれい。
レベル上がってきたな。。。

ということで今回は無限スクロールさせちゃいましょう。

そこら辺に書いてあるコードだといろいろエラーが出たり、
動かなかったりする。

さらに簡単に書いてみた。

タブ版ね。


<template>


    <div>



        <el-tabs type="card" @tab-click="handleClick" :stretch="true" v-model="activeTab">
            <el-tab-pane label="new" name="first" :disabled="loading">
                <span slot="label"><i class="fas fa-magic"></i> 新着占い</span>
            </el-tab-pane>
            <el-tab-pane label="ninki" name="second" :disabled="loading">
                <span slot="label"><i class="fas fa-crown"></i> 人気占い</span>
            </el-tab-pane>
        </el-tabs>


        <div v-for="(v,key) in list">
            {{v.id}} : {{v.message}}
        </div>


        <infinite-loading force-use-infinite-wrapper="true" @infinite="onInfinite" ref="infiniteLoading" :distance="500" no-more="これ以上ない"></infinite-loading>

    </div>




</template>


<script>
    import axios from 'axios'
    import InfiniteLoading from 'vue-infinite-loading'

    Vue.use(InfiniteLoading, {
        slots: {
            noMore: 'すべて読み込みました', // you can pass a string value
            noResults: '読み込み完了しています', // you can pass a string value
        },
    });



    export default {
        data() {
            return {


                activeTab:"first",
                storageName:'contactShow',
                targetUrl:'/contact/show/?kanteizumi=1&order=id',//パラメーターが無い時も必ず ? をつけて終わらせる



                loading:false,//タブを disabled にする
                query:'',
                nextUrl:'',
                list:[],//ここに取得したデータが入る


                InfiniteLoadingOption: {
                    total:0,//データが合計いくつあるか
                    page:1,
                    nextPage:1,
                    lastPage:10,
                }

            };
        },

        created () {



        },

        mounted() {

            if(this.$route.query.page){

                if (sessionStorage.getItem(this.storageName + '_list')) {
                    this.readPageStorage();
                }

            } else {
                //他の画面を経由して来た場合は検索結果をリセット
                this.clearStorage();
            }

        },


        methods: {

            //検索条件を組み立てる
            makeSearch() {

                //例えば、ここで targetUrl を元に 並び順などを 組み立てますか。
                this.loading = true;


                //選択されているタブに応じて取得先を切り替える
                if(this.activeTab == 'first'){
                    this.targetUrl = '/contact/show/?kanteizumi=1&order=id';
                }

                if(this.activeTab == 'second'){
                    this.targetUrl = '/contact/show/?order=id';
                }


                for( var key in this.$route.query) {
                    if(key != 'page' && key != 'y'){
                        this.targetUrl += "&"+key+"="+this.$route.query[key];
                        this.query += "&"+key+"="+this.$route.query[key];
                    }
                }

                //検索条件を保存
                this.nextUrl = this.targetUrl + "&page=" + this.InfiniteLoadingOption.nextPage;
            },


            //現在のページ情報を保存する
            addPageStorage() {
                sessionStorage.setItem(this.storageName + '_list',JSON.stringify(this.list));
                sessionStorage.setItem(this.storageName + '_total',this.InfiniteLoadingOption.total);
                sessionStorage.setItem(this.storageName + '_page',this.InfiniteLoadingOption.page);
                sessionStorage.setItem(this.storageName + '_nextPage',this.InfiniteLoadingOption.nextPage);
                sessionStorage.setItem(this.storageName + '_activeTab',this.activeTab);
            },


            //戻ってきた場合、ページ情報を復元する
            readPageStorage() {
                this.list = JSON.parse(sessionStorage.getItem(this.storageName + '_list'));
                this.InfiniteLoadingOption.total = sessionStorage.getItem(this.storageName + '_total');
                this.InfiniteLoadingOption.page = sessionStorage.getItem(this.storageName + '_page');
                this.InfiniteLoadingOption.nextPage = sessionStorage.getItem(this.storageName + '_nextPage');
                this.activeTab = sessionStorage.getItem(this.storageName + '_activeTab');
            },

            //session storage を 削除。 clear sessionStorage.clear() は使わない。
            clearStorage() {
                sessionStorage.removeItem(this.storageName + '_list');
                sessionStorage.removeItem(this.storageName + '_total');
                sessionStorage.removeItem(this.storageName + '_page');
                sessionStorage.removeItem(this.storageName + '_nextPage');
                sessionStorage.removeItem(this.storageName + '_activeTab');
            },


            onInfinite() {


                this.makeSearch();


                //
                if (this.InfiniteLoadingOption.page < this.InfiniteLoadingOption.lastPage) {


                    axios.get(this.nextUrl).then(e => {


                        if (e.data.data.length) {


                            this.InfiniteLoadingOption.total = e.data.total;

                            //クエリーがある場合はクエリーを含み次のページのURLとする。
                            var idou = location.pathname + "?page=" + e.data.current_page + "&y=" + window.scrollY + this.query.replace(/\//g,"");
                            //現在のURLを置き換える
                            this.$router.replace({
                                path: idou
                            });


                            this.list = this.list.concat(e.data.data);


                            this.$refs.infiniteLoading.stateChanger.loaded();
                            this.InfiniteLoadingOption.nextPage++;
                            this.addPageStorage();


                            if (e.data.current_page >= e.data.last_page) {
                                console.log("最終ページまで行ったので処理しません");
                                this.$refs.infiniteLoading.stateChanger.complete();
                            }




                        } else {
                            console.log("データがないので終了します");
                            this.$refs.infiniteLoading.stateChanger.complete();
                        }

                        this.loading = false;


                    }).catch((error) => {
                        console.log("エラー");

                    });

                } else {

                    this.$refs.infiniteLoading.stateChanger.complete();
                    console.log("全ページ読み込み完了");
                }
            },

            handleClick(tab, event) {


                this.InfiniteLoadingOption.nextPage = 1;
                this.InfiniteLoadingOption.total = 0;
                this.InfiniteLoadingOption.page = 1;

                this.list = [];

                // //現在のURLを置き換える
                this.$router.push({
                    path: location.pathname
                });
                //
                this.$refs.infiniteLoading.stateChanger.reset();
                this.clearStorage();


            }
        }

    }
</script>


追記: sessionStorage.clear() を使わずに


            clearStorage(){
                sessionStorage.removeItem('top_list');
                sessionStorage.removeItem('top_total');
                sessionStorage.removeItem('top_page');
                sessionStorage.removeItem('top_nextPage');
                sessionStorage.removeItem('top_activeTab');
            },


上記のような感じで削除していこう。
でないとバグる。


<template>


    <div>

        <div v-for="(v,key) in list">
            <a v-touch="$root.linkTo('/f/'+v.id+'/')">{{v.name}}</a>
        </div>

        <infinite-loading force-use-infinite-wrapper="true" @infinite="onInfinite" ref="infiniteLoading" :distance="500" no-more="これ以上ない" ></infinite-loading>
    </div>




</template>

<script>

    import axios from 'axios'
    import InfiniteLoading from 'vue-infinite-loading';

    Vue.use(InfiniteLoading, {
        slots: {
            noMore: 'すべて読み込みました', // you can pass a string value
            noResults: '読み込み完了しています', // you can pass a string value
        },
    });

    export default {

        components: {
            InfiniteLoading,
        },

        data () {
            return {

                list:[],//ここに取得したデータが入る

                // targetUrl:'/fortune/show/?order=graph_type',
                targetUrl:'/fortune/show/?',//条件がない場合は ? で終わらせておく
                nextUrl:'',


                InfiniteLoadingOption: {
                    total:0,//データが合計いくつあるか
                    page:1,
                    nextPage:1,
                    lastPage:10,
                }

            };
        },


        created () {


        },
        mounted() {

            if(this.$route.query.page){

                console.log("ページ遷移先から戻ってきました。");
                if (sessionStorage.list) {
                    this.readPageStorage();
                }

            } else {
                //他の画面を経由して来た場合は検索結果をリセット
                sessionStorage.clear();
            }


        },

        methods: {

            //検索条件を組み立てる
            makeSearch() {

                //例えば、ここで targetUrl を元に 並び順などを 組み立てますか。

                //検索条件を保存
                this.nextUrl = this.targetUrl + "&page=" + this.InfiniteLoadingOption.nextPage;
            },


            //現在のページ情報を保存する
            addPageStorage(){
                sessionStorage.list = JSON.stringify(this.list);
                sessionStorage.total = this.InfiniteLoadingOption.total;
                sessionStorage.page = this.InfiniteLoadingOption.page;
                sessionStorage.nextPage = this.InfiniteLoadingOption.nextPage;
            },

            //戻ってきた場合、ページ情報を復元する
            readPageStorage(){
                this.list =  JSON.parse(sessionStorage.getItem('list'));
                this.InfiniteLoadingOption.nextPage = sessionStorage.getItem('nextPage');
                this.InfiniteLoadingOption.total = sessionStorage.getItem('total');
                this.InfiniteLoadingOption.page = sessionStorage.getItem('page');
            },



            onInfinite() {

                this.makeSearch();

                if (this.InfiniteLoadingOption.page < this.InfiniteLoadingOption.lastPage) {

                    axios.get(this.nextUrl).then(e => {

                        if (e.data.data.length) {


                            this.InfiniteLoadingOption.total = e.data.total;


                            //現在のURLを置き換える
                            this.$router.replace({
                                path: location.pathname + "?page=" + e.data.current_page + "&y=" + window.scrollY
                            });


                            this.list = this.list.concat(e.data.data);
                            this.$refs.infiniteLoading.stateChanger.loaded();
                            this.InfiniteLoadingOption.nextPage++;
                            this.addPageStorage();


                            if(e.data.current_page >= e.data.last_page){
                                console.log("最終ページまで行ったので処理しません");
                                this.$refs.infiniteLoading.stateChanger.complete();
                            }

                        } else {
                            console.log("データがないので終了します");
                            this.$refs.infiniteLoading.stateChanger.complete();
                        }



                    }).catch((error) => {
                        console.log("エラー");

                    });

                } else {

                    this.$refs.infiniteLoading.stateChanger.complete();
                    console.log("全ページ読み込み完了");
                }
            }

        }


    }
</script>

検索条件を変更するには


this.$refs.infiniteLoading.stateChanger.reset();

追記。
pulltoとかのライブラリと併用しようとしたがうまくいかない。

オプション変更したい場合

hoge.vue

    // app.js でも import して、さらに このMugen.vue でもimportするのが味噌
    import InfiniteLoading from 'vue-infinite-loading';

    Vue.use(InfiniteLoading, {
        slots: {
            noMore: 'すべて読み込みました', // you can pass a string value
            noResults: '読み込み完了しています', // you can pass a string value

        },
    });

参考
https://blog.csdn.net/zfangls/article/details/72727198

インストール

npm install vue-infinite-loading --save

app.js


import InfiniteLoading from 'vue-infinite-loading';

コード

elementui を使っているとバグるみたい。
読み込みまくって止まらない。ということで、

force-use-infinite-wrapper="true"

を忘れずに。


<template>
    <div class="list-con">

        <div class="list" v-for="(item,key) in list">
            <span v-text="key+1"></span>
            <p>
                <a :href="item.url">{{item.title}}</a>
            </p>
        </div>

        <infinite-loading force-use-infinite-wrapper="true" @infinite="onInfinite" ref="infiniteLoading" :distance="1000"></infinite-loading>

    </div>
</template>


<script>

    // app.js でも import して、さらに このMugen.vue でもimportするのが味噌
    import InfiniteLoading from 'vue-infinite-loading';

    //あえてページは指定しない
    const api = 'https://hn.algolia.com/api/v1/search_by_date?tags=story&page=';

    export default {
        components: {
            InfiniteLoading,
        },
        data() {
            return {
                list:[]
            }
        },
        mounted: function() {

        },
        created(){

        },
        methods: {


            onInfinite() {

                let page = this.list.length / 20 + 1;

                console.log(page);

                axios.get(api+page)
                    .then(function (res) {


                        if (res.data.hits.length) {



                            //今まで読みこんだ配列に、新しく読み込んだ配列を結合
                            this.list = this.list.concat(res.data.hits);

                            // this.$router.push("/mugen?page=" + page);

                            console.log("現在のスクロール位置" + window.scrollY);
                            this.$router.push({
                                path: "/mugen?page=" + page + "&y=" + window.scrollY,
                            });

                            this.$refs.infiniteLoading.stateChanger.loaded();


                            //10ページ目まで読み込んだらそれ以上は読み込ませない
                            if (this.list.length / 20 === 10) {
                                console.log("大量にデータがあるのでここで終了しておきます。");
                                this.$refs.infiniteLoading.stateChanger.complete();
                            }
                        } else {
                            //データがなけりゃそれ以上読み込ませない
                            console.log("全て読み込ました");
                            this.$refs.infiniteLoading.stateChanger.complete();
                        }

                        //    bind しておくことで、 axios、then  内で this や page を 使えるようにする
                    }.bind(this).bind(page))
                    .catch(function (error) {
                        console.log(error);
                    });

            }
        }
    }
</script>



<style scoped>
    .list{
        overflow:hidden;
        margin:20px 0;
    }
    span{
        float: left;
        margin-right: 5px;
    }
    p{
        float: left;
    }
</style>


おまけ

さらに早く読み込みたい!
ってときは distance を指定する。


        <infinite-loading @infinite="onInfinite" ref="infiniteLoading" :distance="1000">
            <span slot="no-more">すべて読み込みました</span>
        </infinite-loading>

laravel との併用

laravel のコントローラーで


//    ユーザー一覧を表示
    public function show()
    {

        $res = User::query()->paginate(25);

        return response()->json($res);



    }

これでユーザー情報を 25人分表示せよと。


<template>

    <div>

        <div class="list-con">
            <div class="list" v-for="(item,key) in list">
                <p>
                    {{item.id}} : {{item.name}}
                </p>
            </div>
            <infinite-loading @infinite="onInfinite" ref="infiniteLoading" :distance="500">
                <span slot="no-more">すべて読み込みました</span>
            </infinite-loading>
        </div>

    </div>


</template>
<script>

    // app.js でも import して、さらに このMugen.vue でもimportするのが味噌
    import InfiniteLoading from 'vue-infinite-loading';

    export default {
        components: {
            InfiniteLoading,
        },
        data() {
            return {
                list:[],
                nextUrl:'/user/show/'
            }
        },
        mounted: function() {

        },
        created(){

        },
        methods: {

            onInfinite() {


                axios.get(this.nextUrl).then(e => {

                    // console.log(e.data);
                    // //メインデータ
                    // console.log(e.data.data);
                    //
                    //
                    // //次のページのURL
                    // console.log(e.data.next_page_url);


                    if (e.data.data.length) {


                        //今まで読みこんだ配列に、新しく読み込んだ配列を結合
                        this.list = this.list.concat(e.data.data);
                        this.$refs.infiniteLoading.stateChanger.loaded();
                        this.$router.push("/show?page=" + e.data.current_page);


                        this.nextUrl = e.data.next_page_url;

                        //現在のページ と 最後のページが同一なら終了
                        if (e.data.current_page == e.data.last_page) {
                            alert("読み込み完了しました");
                            this.$refs.infiniteLoading.stateChanger.complete();
                        }

                    } else {
                        //データがなけりゃそれ以上読み込ませない
                        this.$refs.infiniteLoading.stateChanger.complete();
                    }

                    // console.log(e.data);

                }).catch((error) => {

                    console.log(error);
                    console.log("エラー");

                });

            }
        }
    }
</script>



<style scoped>
    .list{
        overflow:hidden;
        margin:20px 0;
    }
    span{
        float: left;
        margin-right: 5px;
    }
    p{
        float: left;
    }
</style>


何が素晴らしいって、laravel の paginate は最後のページ数とかも計算してくれるので簡単にvue.jsと連携できるのがすごい。

セッションストレージでキャッシュをとっておく


<template>

    <div>


        <div style="margin-top: 45px;">
            現在のページ数 {{page}}
        </div>


        <div class="list" v-for="(item,key) in list">
            <span v-text="key+1"></span>
            <p>
                <a v-touch="$root.linkTo('/user/10049/')">{{item.title}}</a>
            </p>
        </div>



        <infinite-loading force-use-infinite-wrapper="true" @infinite="onInfinite" ref="infiniteLoading" :distance="1000"></infinite-loading>

    </div>

</template>


<script>

    // app.js でも import して、さらに このMugen.vue でもimportするのが味噌
    import InfiniteLoading from 'vue-infinite-loading';

    //あえてページは指定しない
    const api = 'https://hn.algolia.com/api/v1/search_by_date?tags=story&page=';

    export default {
        components: {
            InfiniteLoading
        },
        data() {
            return {
                list:[],
                page:1,
            }
        },

        mounted() {


            //ブラウザ戻るできた場合は必ず page があるので。
            //これでバックしてきたと判断する


            if(this.$route.query.page){

                // console.log("ページあるで");
                if (sessionStorage.list) {
                    this.list =  JSON.parse(sessionStorage.getItem('list'));
                    console.log(this.list);
                }

                if (sessionStorage.page) {
                    this.page = sessionStorage.getItem('page');
                    console.log(this.page);
                }

            } else {
                //他の画面を経由して来た場合は検索結果をリセット
                sessionStorage.clear();
            }


        },

        created(){



        },
        methods: {



            onInfinite() {


                if(this.page < 10){

                    this.page = this.list.length / 20 + 1;
                    sessionStorage.page = this.page;

                    axios.get(api+this.page).then(res => {

                        if (res.data.hits.length) {

                            //今まで読みこんだ配列に、新しく読み込んだ配列を結合
                            this.list = this.list.concat(res.data.hits);

                            sessionStorage.list = JSON.stringify(this.list);

                            this.$router.push({
                                path: "/mugen?page=" + this.page + "&y=" + window.scrollY,
                            });

                            this.$refs.infiniteLoading.stateChanger.loaded();

                            //10ページ目まで読み込んだらそれ以上は読み込ませない
                            if (this.page >= 10) {
                                console.log("大量にデータがあるのでここで終了しておきます。");
                                this.$refs.infiniteLoading.stateChanger.complete();
                            }
                        } else {
                            //データがなけりゃそれ以上読み込ませない
                            console.log("全て読み込ました");
                            this.$refs.infiniteLoading.stateChanger.complete();
                        }


                        console.log("成功");
                    }).catch((error) => {
                        console.log("エラー");
                    });

                } else {
                    console.log("セッションストレージですでにマックス。");
                    this.$refs.infiniteLoading.stateChanger.complete();
                }
            },

            refresh(loaded) {

                alert("再読込完了");

                loaded('done');
            }
        }
    }
</script>



<style scoped>
    .list{
        overflow:hidden;
        margin:20px 0;
    }
    span{
        float: left;
        margin-right: 5px;
    }
    p{
        float: left;
    }
</style>

8
9
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
8
9