InfiniteLoading 無限スクロール vue.js

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>

```