mirror of
https://github.com/nuxt/nuxt.git
synced 2025-01-18 17:35:57 +00:00
feat(nuxt): slow down loading indicator when approaching 100% (#25119)
This commit is contained in:
parent
ecc4c8e0c5
commit
90ca0e8797
@ -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`).
|
- `height`: Height of the loading bar, in pixels (default `3`).
|
||||||
- `duration`: Duration of the loading bar, in milliseconds (default `2000`).
|
- `duration`: Duration of the loading bar, in milliseconds (default `2000`).
|
||||||
- `throttle`: Throttle the appearing and hiding, in milliseconds (default `200`).
|
- `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
|
::callout
|
||||||
This component is optional. :br
|
This component is optional. :br
|
||||||
@ -42,3 +43,7 @@ To achieve full customization, you can implement your own one based on [its sour
|
|||||||
::callout
|
::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.
|
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.
|
||||||
|
::
|
||||||
|
@ -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.
|
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.
|
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
|
## Properties
|
||||||
|
|
||||||
### `isLoading`
|
### `isLoading`
|
||||||
@ -38,3 +44,16 @@ Set the `progress` value to `100`, stop all timers and intervals then reset the
|
|||||||
### `clear()`
|
### `clear()`
|
||||||
|
|
||||||
Used by `finish()`. Clear all timers and intervals used by the composable.
|
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>
|
||||||
|
```
|
||||||
|
@ -19,12 +19,17 @@ export default defineComponent({
|
|||||||
color: {
|
color: {
|
||||||
type: [String, Boolean],
|
type: [String, Boolean],
|
||||||
default: 'repeating-linear-gradient(to right,#00dc82 0%,#34cdfe 50%,#0047e1 100%)'
|
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 }) {
|
setup (props, { slots, expose }) {
|
||||||
const { progress, isLoading, start, finish, clear } = useLoadingIndicator({
|
const { progress, isLoading, start, finish, clear } = useLoadingIndicator({
|
||||||
duration: props.duration,
|
duration: props.duration,
|
||||||
throttle: props.throttle
|
throttle: props.throttle,
|
||||||
|
estimatedProgress: props.estimatedProgress,
|
||||||
})
|
})
|
||||||
|
|
||||||
expose({
|
expose({
|
||||||
|
@ -7,10 +7,12 @@ export type LoadingIndicatorOpts = {
|
|||||||
duration: number
|
duration: number
|
||||||
/** @default 200 */
|
/** @default 200 */
|
||||||
throttle: number
|
throttle: number
|
||||||
}
|
/**
|
||||||
|
* You can provide a custom function to customize the progress estimation,
|
||||||
function _increase (progress: Ref<number>, num: number) {
|
* which is a function that receives the duration of the loading bar (above)
|
||||||
progress.value = Math.min(100, progress.value + num)
|
* 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>) {
|
function _hide (isLoading: Ref<boolean>, progress: Ref<number>) {
|
||||||
@ -32,14 +34,20 @@ export type LoadingIndicator = {
|
|||||||
clear: () => void
|
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> = {}) {
|
function createLoadingIndicator (opts: Partial<LoadingIndicatorOpts> = {}) {
|
||||||
const { duration = 2000, throttle = 200 } = opts
|
const { duration = 2000, throttle = 200 } = opts
|
||||||
|
const getProgress = opts.estimatedProgress || defaultEstimatedProgress
|
||||||
const nuxtApp = useNuxtApp()
|
const nuxtApp = useNuxtApp()
|
||||||
const progress = ref(0)
|
const progress = ref(0)
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
const step = computed(() => 10000 / duration)
|
let done = false
|
||||||
|
let rafId: number
|
||||||
|
|
||||||
let _timer: any = null
|
|
||||||
let _throttle: any = null
|
let _throttle: any = null
|
||||||
|
|
||||||
const start = () => set(0)
|
const start = () => set(0)
|
||||||
@ -54,30 +62,42 @@ function createLoadingIndicator (opts: Partial<LoadingIndicatorOpts> = {}) {
|
|||||||
if (throttle && import.meta.client) {
|
if (throttle && import.meta.client) {
|
||||||
_throttle = setTimeout(() => {
|
_throttle = setTimeout(() => {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
_startTimer()
|
_startProgress()
|
||||||
}, throttle)
|
}, throttle)
|
||||||
} else {
|
} else {
|
||||||
isLoading.value = true
|
isLoading.value = true
|
||||||
_startTimer()
|
_startProgress()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function finish () {
|
function finish () {
|
||||||
progress.value = 100
|
progress.value = 100
|
||||||
|
done = true
|
||||||
clear()
|
clear()
|
||||||
_hide(isLoading, progress)
|
_hide(isLoading, progress)
|
||||||
}
|
}
|
||||||
|
|
||||||
function clear () {
|
function clear () {
|
||||||
clearInterval(_timer)
|
|
||||||
clearTimeout(_throttle)
|
clearTimeout(_throttle)
|
||||||
_timer = null
|
cancelAnimationFrame(rafId)
|
||||||
_throttle = null
|
_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) {
|
if (import.meta.client) {
|
||||||
_timer = setInterval(() => { _increase(progress, step.value) }, 100)
|
rafId = requestAnimationFrame(step)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user