feat(nuxt): slow down loading indicator when approaching 100% (#25119)

This commit is contained in:
Ivan Kalachikov 2024-01-30 00:19:32 +08:00 committed by GitHub
parent ecc4c8e0c5
commit 90ca0e8797
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 63 additions and 14 deletions

View File

@ -33,6 +33,7 @@ You can pass custom HTML or components through the loading indicator's default s
- `height`: Height of the loading bar, in pixels (default `3`).
- `duration`: Duration of the loading bar, in milliseconds (default `2000`).
- `throttle`: Throttle the appearing and hiding, in milliseconds (default `200`).
- `estimatedProgress`: By default Nuxt will back off as it approaches 100%. You can provide a custom function to customize the progress estimation, which is a function that receives the duration of the loading bar (above) and the elapsed time. It should return a value between 0 and 100.
::callout
This component is optional. :br
@ -42,3 +43,7 @@ To achieve full customization, you can implement your own one based on [its sour
::callout
You can hook into the underlying indicator instance using [the `useLoadingIndicator` composable](/docs/api/composables/use-loading-indicator), which will allow you to trigger start/finish events yourself.
::
::callout
The loading indicator's speed gradually decreases after reaching a specific point controlled by `estimatedProgress`. This adjustment provides a more accurate reflection of longer page loading times and prevents the indicator from prematurely showing 100% completion.
::

View File

@ -13,6 +13,12 @@ links:
A composable which returns the loading state of the page. Used by [`<NuxtLoadingIndicator>`](/docs/api/components/nuxt-loading-indicator) and controllable.
It hooks into [`page:loading:start`](/docs/api/advanced/hooks#app-hooks-runtime) and [`page:loading:end`](/docs/api/advanced/hooks#app-hooks-runtime) to change its state.
## Parameters
- `duration`: Duration of the loading bar, in milliseconds (default `2000`).
- `throttle`: Throttle the appearing and hiding, in milliseconds (default `200`).
- `estimatedProgress`: By default Nuxt will back off as it approaches 100%. You can provide a custom function to customize the progress estimation, which is a function that receives the duration of the loading bar (above) and the elapsed time. It should return a value between 0 and 100.
## Properties
### `isLoading`
@ -38,3 +44,16 @@ Set the `progress` value to `100`, stop all timers and intervals then reset the
### `clear()`
Used by `finish()`. Clear all timers and intervals used by the composable.
## Example
```ts
<script setup lang="ts">
const { progress, isLoading, start, finish, clear } = useLoadingIndicator({
duration: 2000,
throttle: 200,
// This is how progress is calculated by default
estimatedProgress: (duration, elapsed) => (2 / Math.PI * 100) * Math.atan(elapsed / duration * 100 / 50)
})
</script>
```

View File

@ -19,12 +19,17 @@ export default defineComponent({
color: {
type: [String, Boolean],
default: 'repeating-linear-gradient(to right,#00dc82 0%,#34cdfe 50%,#0047e1 100%)'
}
},
estimatedProgress: {
type: Function as unknown as () => (duration: number, elapsed: number) => number,
required: false
},
},
setup (props, { slots, expose }) {
const { progress, isLoading, start, finish, clear } = useLoadingIndicator({
duration: props.duration,
throttle: props.throttle
throttle: props.throttle,
estimatedProgress: props.estimatedProgress,
})
expose({

View File

@ -7,10 +7,12 @@ export type LoadingIndicatorOpts = {
duration: number
/** @default 200 */
throttle: number
}
function _increase (progress: Ref<number>, num: number) {
progress.value = Math.min(100, progress.value + num)
/**
* You can provide a custom function to customize the progress estimation,
* which is a function that receives the duration of the loading bar (above)
* and the elapsed time. It should return a value between 0 and 100.
*/
estimatedProgress?: (duration: number, elapsed: number) => number
}
function _hide (isLoading: Ref<boolean>, progress: Ref<number>) {
@ -32,14 +34,20 @@ export type LoadingIndicator = {
clear: () => void
}
function defaultEstimatedProgress (duration: number, elapsed: number): number {
const completionPercentage = elapsed / duration * 100
return (2 / Math.PI * 100) * Math.atan(completionPercentage / 50)
}
function createLoadingIndicator (opts: Partial<LoadingIndicatorOpts> = {}) {
const { duration = 2000, throttle = 200 } = opts
const getProgress = opts.estimatedProgress || defaultEstimatedProgress
const nuxtApp = useNuxtApp()
const progress = ref(0)
const isLoading = ref(false)
const step = computed(() => 10000 / duration)
let done = false
let rafId: number
let _timer: any = null
let _throttle: any = null
const start = () => set(0)
@ -54,30 +62,42 @@ function createLoadingIndicator (opts: Partial<LoadingIndicatorOpts> = {}) {
if (throttle && import.meta.client) {
_throttle = setTimeout(() => {
isLoading.value = true
_startTimer()
_startProgress()
}, throttle)
} else {
isLoading.value = true
_startTimer()
_startProgress()
}
}
function finish () {
progress.value = 100
done = true
clear()
_hide(isLoading, progress)
}
function clear () {
clearInterval(_timer)
clearTimeout(_throttle)
_timer = null
cancelAnimationFrame(rafId)
_throttle = null
}
function _startTimer () {
function _startProgress () {
done = false
let startTimeStamp: number
function step (timeStamp: number): void {
if (done) { return }
startTimeStamp ??= timeStamp
const elapsed = timeStamp - startTimeStamp
progress.value = Math.max(0, Math.min(100, getProgress(duration, elapsed)))
rafId = requestAnimationFrame(step)
}
if (import.meta.client) {
_timer = setInterval(() => { _increase(progress, step.value) }, 100)
rafId = requestAnimationFrame(step)
}
}