vue-router-lazy-loading

by gongzza

HTML

<script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

<div id="app">
  <router-link to="/">Home</router-link>
  <router-link to="/welcome">Welcome</router-link>
  <router-link to="/hello">Hello</router-link>
  
  <router-view></router-view>
</div>

<template id="loading">
  <transition name="loading">
    <div class="loading">
      <div class="spinner"></div>
    </div>
  </transition>
</template>

<template id="loader">
  <div v-if="isLoading" :is="component"></div>
</template>

CSS

.loading-enter-active, .loading-leave-active {
  transition: opacity .5s
}
.loading-enter, .loading-leave-to {
  opacity: 0
}
.loading {
  display: flex;
  position: fixed;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  justify-content: center;
  align-items: center;
  background-color: rgba(0, 0, 0, 0.3);
}
.spinner {
    border: 16px solid #f3f3f3; /* Light grey */
    border-top: 16px solid #3498db; /* Blue */
    border-radius: 50%;
    width: 60px;
    height: 60px;
    animation: spin 2s linear infinite;
}
@keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
}

JavaScript

// pages
const Home = { template: '<div>Home</div>' }
const Welcome = { template: '<div>Welcome to Your Vue.js App</div>' }
const Hello = { template: '<div>Hello World!!</div>' }

// https://webpack.js.org/guides/code-splitting/
// { path: '/', component: () => import(/* webpackChunkname: 'home' */'./Home.vue') }
const router = new VueRouter({
  routes: [
    { path: '/', component: () => new Promise(resolve => setTimeout(() => resolve(Home), 1000)) },
    { path: '/welcome', component: () => new Promise(resolve => setTimeout(() => resolve(Welcome), 2000)) },
    { path: '/hello', component: () => new Promise(resolve => setTimeout(() => resolve(Hello), 3000)) }
  ]
})

const Loading = { template: '#loading' }
const Loader = {
	name: 'loader',
  template: '#loader',
  props: {
    component: Object
  },
  data() {
    return {
      isLoading: false
    }
  },
  mounted() {
    document.body.appendChild(this.$el)
  },
  destroyed() {
    this.$el.remove()
  },
  methods: {
    start() {
      this.isLoading = true
    },
    stop() {
      this.isLoading = false
    }
  }
}

class LoadingHelper {
  constructor(component, delay) {
    this.loading = false
    this.delay = delay

    const Ctor = Vue.extend(Loader)
    this.loader = new Ctor({
      el: document.createElement('div'),
      propsData: {
        component, delay
      }
    })
  }

  get isLoading() {
    return this.loading
  }

  start() {
    this.loading = true
    
    setTimeout(() => {
      if (this.isLoading) {
        this.loader.start()
      }
    }, this.delay)
  }

  stop() {
    this.loader.stop()
    this.loading = false
  }
}

const VueLoading = {
  install(Vue, {router, component = Loading, delay = 200} = {}) {

    const loading = new LoadingHelper(component, delay)

    if (router) {
      router.beforeEach((to, from, next) => {
        loading.start()
        next()
      })
      router.beforeResolve((to, from, next) => {
        loading.stop()
        next()
      })
    }

   ...