2

I want to increase the loading times on my website. I currently have a list of all my projects and all the projects load on the first pageload. I want to implement lazy loading to those components but I can't seem to find a way to do it.

I have a component called project-card:

<project-card v-bind:project="project" v-bind:key="project.id" v-if="projects" v-for="project in filteredProjects"></project-card>

The template file:

<template>
    <div class="project-card rounded dark:bg-gray-800 bg-white overflow-hidden shadow-lg rounded-lg flex flex-col relative">
        <img class="w-full h-64 object-cover" :src="'/uploads/' + project.image" alt="">
        <div class="px-6 py-4 h-full flex flex-col">
            <div class="project-info block min-h-8">
                <div class="w-full">
                    <p class="tracking-widest text-xs title-font font-medium dark:text-white text-gray-500 mb-1">{{ project.language }}</p>
                </div>
            </div>
            <div class="project-language flex-1">
                <div class="w-5/6 float-left">
                    <p class="font-bold dark:text-white gilroy text-xl">{{ project.name }}</p>
                </div>
            </div>
            <div class="project-description flex-2 space-y-4 py-3 h-full">
                <p class="dark:text-white ">{{ project.description | str_limit(128) }}</p>
            </div>
            <div class="read-more mt-auto">
                <div class="flex items-center flex-wrap ">
                    <button type="button" @click="openModal" class="font-bold text-sm text-indigo-600 hover:text-indigo-500 transition duration-150 ease-in-out hover:text-indigo-900 read-more-button flex items-center focus:outline-none">
                        Lees meer <span class="read-more-arrow ml-2">→</span>
                    </button>
                    <span class="text-gray-600 mr-3 inline-flex items-center lg:ml-auto md:ml-0 ml-auto leading-none text-sm pr-3 py-1 border-r-2 border-gray-300">

                    </span>
                    <span class="text-gray-600 inline-flex items-center leading-none text-sm">
                        <svg xmlns="http://www.w3.org/2000/svg" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" stroke-linecap="round" stroke-linejoin="round" class="w-4 h-4 mr-1">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z" />
                        </svg>
                        {{ project.stargazers_count }}
                    </span>
                </div>
                <project-card-modal :project="project" ref="projectModal"></project-card-modal>
            </div>
        </div>
    </div>
</template>

<script>
    import projectCardModalComponent from "./projectCardModalComponent";

    export default {
        components: {
            projectCardModalComponent
        },
        methods: {
            openModal() {
                this.$refs.projectModal.show = true;
            }
        },

        props: ['project']
    }
</script>

Currently It is looking something like this, so it gives an idea of how it is supposed to turn out. But I want to implement lazy loading to this component. How can I achieve that?

enter image description here

APP.JS

import Vue from "vue";

require('./bootstrap');

window.Vue = require('vue');

Vue.component('project-card', require('./components/projectCardComponent.vue').default);
Vue.component('project-card-modal', require('./components/projectCardModalComponent.vue').default);
Vue.component('contact-form', require('./components/contactFormComponent.vue').default);
Vue.component('contact-form-flash', require('./components/contactFormFlashComponent.vue').default);
Vue.component('project-language-filter-button', require('./components/projectLanguageButtonFilterComponent.vue').default);
Vue.component('project-language-filter-dropdown', require('./components/projectLanguageDropdownFilterComponent.vue').default);
Vue.component('education', require('./components/educationComponent.vue').default);
Vue.component('work-experience', require('./components/workExperienceComponent.vue').default);

Vue.component('sidebar', require('./components/dashboard/dashboardSidebarComponent.vue').default);
Vue.component('projects', require('./components/dashboard/dashboardProjectsComponent.vue').default);

import MugenScroll from 'vue-mugen-scroll'

const app = new Vue({
    el: '#app',
    data: {
        showModal: false,
        projects: [],
        filteredProjects: [],
        languages: [],
        languageFilterKey: 'all',
        workExperiences: [],
        educations: [],

        search: '',
        pageSizeBegin: 0,
        pageSizeEnd: null,

        projectsLoading: false,
    },
    mounted: function() {
        this.getProjects();
        this.getWorkExperiences();
        this.getAllEducations();
        this.getProjectLanguages();
    },
    created() {
        this.getFilteredProjects();
    },
    methods: {
        getProjects: function() {
            axios.get('/api/get/projects')
                .then(response => {
                    this.filteredProjects = this.projects = response.data
                }).catch(err => {
                console.log(err)
            });
        },
        getFilteredProjects: function() {
            for(let i = 0;  i < 6; i++) {
                let count = this.filteredProjects.length + i
            }
        },
        getProjectLanguages: function() {
            axios.get('/api/get/project/languages')
                .then(response => {
                    this.languages = response.data
                }).catch(err => {
                console.log(err)
            });
        },
        selectedLanguage: function() {
            if(this.languageFilterKey !== null) {
                this.languages.forEach((item) => {
                    item.active = item.language === this.languageFilterKey;
                });
            } else {
                this.languageFilterKey = null
            }
        },
        filterProjectsByLanguage () {
            if(this.languageFilterKey === 'all') {
                this.filteredProjects = this.projects;
            } else {
                this.filteredProjects = this.projects.filter((project) => {
                    return this.languageFilterKey === project.language
                });
            }
        },
        getWorkExperiences: function() {
            axios.get('/api/get/workexperiences')
                .then(response => {
                    this.workExperiences = response.data
                }).catch(err => {
                console.log(err)
            });
        },
        getAllEducations: function() {
            axios.get('/api/get/educations')
                .then(response => {
                    this.educations = response.data
                }).catch(err => {
                console.log(err)
            });
        },
        amountOnChange(event) {
            if(!event.target.value) {
                this.pageSizeEnd = null;
            } else {
                this.pageSizeEnd = event.target.value;
            }
        }
    },
    computed: {
        filteredList() {
            if(!this.pageSizeEnd) {
                return this.projects.filter((project) => {
                    return  project.name.toLowerCase().includes(this.search.toLowerCase()) || project.language.toLowerCase().includes(this.search.toLowerCase())
                })
            }
            return this.projects.filter((project) => {
                return  project.name.toLowerCase().includes(this.search.toLowerCase()) || project.language.toLowerCase().includes(this.search.toLowerCase())
            }).slice(this.pageSizeBegin, this.pageSizeEnd)

        },
    }
})

Vue.filter('str_limit', function (value, size) {
    if (!value) return '';
    value = value.toString();

    if (value.length <= size) {
        return value;
    }
    return value.substr(0, size) + '...';
});

1 Answer 1

0

I think what you want is actually infinite scrolling.

A lot of libs are doing that, my favorite being vue-mugen-scroll.

Take a look at their demo, I think it's close to your use case.

var vm = new Vue({
  el: '#vue-instance',
  data: {
    posts: [],
    loading: false
  },
  created() {
    this.getPosts()
  },
    methods: {
    getPosts() {
        for (var i = 0; i < 16; i++) {
                var count = this.posts.length + i
        this.posts.push({
          title: 'title ' + count
        })
      }
    }
  }
});
<script src="https://unpkg.com/[email protected]/dist/vue-mugen-scroll.min.js"></script>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="vue-instance" class="container">
      <div class="row">
        <div  class="col-sm-6" v-for="(post, index) in posts">
        <div class="card m-4" style="width: 18rem;">
          <img class="card-img-top" src="https://via.placeholder.com/350x150">
          <div class="card-body">
            <h5 class="card-title"><strong>{{ post.title }}</strong></h5>
            </div>
          </div>
        </div>  
      </div>
      <mugen-scroll :handler="getPosts" :should-handle="!loading">
        loading...
      </mugen-scroll>
</div>

Sign up to request clarification or add additional context in comments.

3 Comments

I dont quite understand how I can implement this in my use case. Can you maybe demonstrate? I updated the question with the app.js file
Seems like you already have your own pagination system with pageSizeBegin and pageSizeEnd.
That for a different set of projects.