rstacruz / nprogress

For slim progress bars like on YouTube, Medium, etc
http://ricostacruz.com/nprogress
MIT License
25.99k stars 1.81k forks source link

Feature request: delay option #169

Open fridays opened 7 years ago

fridays commented 7 years ago

It would be nice to have a delay option, which will only show the progress bar if .done was not already called within the delay time:

NProgress.configure({delay: 80}) // Only show if not done within 80ms
steida commented 7 years ago

https://github.com/este/next/blob/aa41364278e7d01a4a77319111b6fdf78935ecc3/components/loading-bar.js

rstacruz commented 7 years ago

you can implement this with something like lodash.debounce:

const debounce = require('lodash.debounce')

const start = debounce(NProgress.start, 80)
rstacruz commented 7 years ago

On second thought, that doesn't really cover it!

MickL commented 6 years ago

Looking forward for this great new feature! Use case:

A singlepage-app may have pages with zero loading time but the progress bar is shown anyway which is kind of confusing to the user. Would be nice to have a delay exactly as @fridays described: Only show the progress bar if NProgress.done() has not been called within this time.

I would recommend to set the delay on NProgress default to 50-250ms.

Currently i have to do it manually which is not a big deal but integrated within NProgress would be much cleaner:

progressTimeout;

// [...]

switch (routeEvent) {
     case NavigationStart:
          this.progressTimeout = setTimeout(() => {
               NProgress.start();
          }, 250);
          break;
     case NavigationEnd:
     case NavigationCancel:
     case NavigationError:
          clearTimeout(this.progressTimeout);
          NProgress.done();
          break;
}
fracz commented 5 years ago

Extend the lib before you use it:

NProgress.doStart = NProgress.start;
NProgress.doDone = NProgress.done;
NProgress.clearDelay = function () {
  if (this.startDelay) {
    clearTimeout(this.startDelay);
    this.startDelay = undefined;
  }
}
NProgress.start = function () {
  this.clearDelay();
  this.startDelay = setTimeout(function () {
    NProgress.doStart();
  }, this.settings.delay || 0);
};
NProgress.done = function () {
  this.clearDelay();
  this.doDone();
};

and then:

NProgress.configure({showSpinner: false, delay: 250}).start();
msurdi commented 5 years ago

I've extended @fracz idea to avoid patching the library, in case anyone else finds it useful:

let progressBarTimeout = null;

const clearProgressBarTimeout = () => {
  if (progressBarTimeout) {
    clearTimeout(progressBarTimeout);
    progressBarTimeout = null;
  }
};

const startProgressBar = () => {
  clearProgressBarTimeout();
  progressBarTimeout = setTimeout(() => {
    nprogress.start();
  }, 200);
};

const stopProgressBar = () => {
  clearProgressBarTimeout();
  nprogress.done();
};

just use startProgressBar and stopProgressBar instead of nprogress.start/done

henrik commented 4 years ago

@msurdi I believe you could simplify that to:

let progressBarTimeout = null;

const startProgressBar = () => {
  clearTimeout(progressBarTimeout);
  progressBarTimeout = setTimeout(nprogress.start, 200);
};

const stopProgressBar = () => {
  clearTimeout(progressBarTimeout);
  nprogress.done();
};

To quote https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/clearTimeout:

Passing an invalid ID to clearTimeout() silently does nothing; no exception is thrown.

And https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/setTimeout says:

It is guaranteed that a timeout ID will never be reused by a subsequent call to setTimeout() or setInterval() on the same object (a window or a worker). However, different objects use separate pools of IDs.

So it's fine to run clearTimeout without checking first – it won't explode on null or old values, and values won't be reused.

erwinstone commented 2 years ago

For typescript users:

let progressBarTimeout: number | undefined = undefined

const startProgressBar = () => {
  clearTimeout(progressBarTimeout)
  progressBarTimeout = setTimeout(nProgress.start, 200)
}

const stopProgressBar = () => {
  clearTimeout(progressBarTimeout)
  nProgress.done()
}
tvanbeek commented 6 months ago

For React you can use this component:

// /src/components/Progress.tsx

import NProgress from "nprogress";
import { useEffect } from "react";

export const Progress = () => {
  // config
  NProgress.configure({
    showSpinner: false,
  });

  useEffect(() => {
    const timeout = setTimeout(NProgress.start, 250); // 250ms

    return () => {
      clearTimeout(timeout);
      NProgress.done();
    };
  });

  return;
}

And then show it when something is loading:

// /src/somePageOrComponent.tsx
import { Progress } from "@/components/Progress";

...
return (
  <>
    {
      somethingIsLoading ? <Progress /> : null;
    }
    <h1>Hello World</h1>
  </>
)
...

Or with Suspense

import { Progress } from "@/components/Progress";

...
<Suspense fallback={<Progress />}>
  <MyAsyncComponent />
</Suspense>
...

In Next.js you can use the same component to show nprogress on route changes. Just create a loading.ts file and export the component (For more information see loading.js file convention for Next.js App Router):

// /src/app/my/route/loading.ts

import { Progress } from "@/components/Progress";
export default Progress;