DEV Community

Discussion on: How to create a Preloader in Next.js

Collapse
 
kylessg profile image
Kyle Johnson

Here's how I did this, rather than being an arbitrary load it actually does detect images being loaded.

import Router, { useRouter } from 'next/router'
import { useEffect, useRef, useState } from 'react'
// @ts-ignore
import AnimatedNumber from 'animated-number-react'
import NProgress from 'nprogress'
let _number = 0

const startingPoint = 20 // Loading progress on route change started
const domLoadedPoint = 35 // Loading progress when page is routed and waiting for images
const totalWait = 200 // How long to wait before going from 99% to 100%
const totalDuration = 200 // How long going from 0% - 100% takes
NProgress.configure({ easing: 'ease', speed: 200 }) // The easing of the progress bar

if (typeof document !== 'undefined') {
  NProgress.set(startingPoint / 100)
}

export default function () {
  const router = useRouter()
  const ref = useRef(router.asPath)
  const [number, setNumber] = useState<number>(35)
  const [duration, setDuration] = useState<number>(totalDuration)
  const updateNumber = (_num: number) => {
    const num = _num === 100 ? 99 : _num
    if (_num === 100) {
      setTimeout(() => {
        document
          .getElementsByTagName('html')[0]
          .classList.remove('nprogress-busy')
        setDuration(50)
        setNumber(100)
        NProgress.set(1)
        _number = 100
      }, totalWait)
    }
    setDuration(parseInt(`${((num - _number) / 100) * totalDuration}`))
    setNumber(num)
    NProgress.set(num / 100)
    _number = num
  }

  useEffect(() => {
    if (typeof document !== 'undefined') {
      const NProgress = require('nprogress')
      let timer: any

      // @ts-ignore
      // eslint-disable-next-line no-inner-declarations
      function load(route) {
        if (ref.current !== route) {
          updateNumber(startingPoint)
        }
      }

      // @ts-ignore
      // eslint-disable-next-line no-inner-declarations
      function stop(route) {
        if (!!route && route === ref.current) {
          return
        }
        if (route) {
          ref.current = route
        }
        const incompleteImages = Array.from(document.images).filter(
          (img) => !img.complete,
        )
        Promise.all(
          incompleteImages.map((img) =>
            new Promise((resolve) => {
              img.onload = img.onerror = resolve
            }).then(() => {
              updateNumber(_number + pointsPerImage)
            }),
          ),
        ).then(() => {
          updateNumber(100)
        })

        updateNumber(domLoadedPoint)

        console.log(_number)
        const pointsPerImage = (100 - domLoadedPoint) / incompleteImages.length
      }

      Router.events.on('routeChangeStart', load)
      Router.events.on('routeChangeComplete', stop)
      Router.events.on('routeChangeError', stop)
    }
    stop()
  }, [])

  return (
    <AnimatedNumber
      className='nprogress-text default'
      value={number}
      formatValue={(v: number) => `${parseInt(`${v}`)}%`}
      duration={duration}
    />
  )
}

Enter fullscreen mode Exit fullscreen mode