Optimal Loading Times Contribute to a Better UX

Introduction

Pursuing shorter wait times and faster feedback is crucial. However, rushing to present information to users may not be optimal. For example:

  • Entering the webpage
  • Feedback: “Loading data…”
  • Displaying the loaded data

This process is simple and straightforward; what could be the issue? The problem lies in timing and duration.

Do Not Present Awkward Experiences to Users

Users cannot comprehend that the loading has ended within 30 milliseconds; they only see a flash of information.

Pursuing immediate feedback as much as possible is good, but if the loading information expires quickly due to “immediate feedback,” it becomes an incomprehensible noise experience. Consider:

  • The form of the loading experience
  • How long users need to understand
  • The necessity and significance of the loading experience

User Perception Experience Principles

The concepts of 0.1 / 1 / 10 seconds stem from Response Times: The 3 Important Limits🔗, which divides the definitions of three types of experiences:

  • 0.1 seconds: the instant feeling
  • 1 second: the feeling of smoothness
  • 10 seconds: the limit of patience

Although different groups, times, individuals, and loading experiences have different standards and impacts, relevant concepts can still be derived.

Different Forms of Loading Experiences

To avoid negative experiences caused by waiting, it is necessary to build an appropriate feedback interface. Specifically, common loading interfaces include the following types, each with its applicable scenarios and trade-offs:

  • Text Loader: A string of text used to inform the loading status.
  • Loader / Spinner: An animated graphic used to inform the loading status.
  • Progress Bar: A progress bar used to indicate loading progress.
  • UI Skeleton: A low-fidelity flickering interface indicating the loading status.

For instance, a UI skeleton can effectively soften the boundary between loading and results, with extremely low cognitive load and often no need for additional animated transitions to ease the experience, but it may not be suitable for every interface.

Minimum Loading Time Code Practice

Consider three components displaying a fruit list, each with response times of 10 / 100 / 1000 milliseconds:

main.vue
<FruitList :response-time="10"></FruitList>
<FruitList :response-time="100"></FruitList>
<FruitList :response-time="1000"></FruitList>
fruitList.vue
<template>
<div class="fruit-list">
<h2>Fruit List</h2>
<div class="actions">
<button @click="fetchFruits" :disabled="loading">
{{ loading ? 'Loading fruit list…' : 'Get fruit list' }}
</button>
<button @click="clear" :disabled="loading">
Clear fruit list
</button>
</div>
<div class="status">
<p v-if="loading">Waiting for response...</p>
</div>
<ul v-if="fruits.length && !loading">
<li v-for="(fruit, index) in fruits" :key="index">{{ fruit }}</li>
</ul>
<p v-else-if="!loading && !fruits.length">No fruit data available.</p>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const { responseTime } = defineProps<{
responseTime: number
}>()
type Fruit = string
function getFruits(): Promise<Fruit[]> {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(['Apple', 'Banana', 'Mango', 'Kiwi'])
}, responseTime)
})
}
const fruits = ref<Fruit[]>([])
const loading = ref(false)
async function fetchFruits() {
loading.value = true
try {
const result = await getFruits()
fruits.value = result
} catch (err: unknown) {
fruits.value = []
} finally {
loading.value = false
}
}
function clear() {
fruits.value = []
}
onMounted(() => {
fetchFruits()
})
</script>

Obviously a 10-millisecond loading time is extremely challenging for most people to comprehend. By creating a promiseWithMinimumTime function, results can be returned at a reasonable timing.

  • Input: Trigger function with minimum n milliseconds trigger time.
  • Calculation:
    • If greater than n, execute.
    • If less than n, execute at n.
main.vue
<FruitList :response-time="10" :min-loading-time="100" />
<FruitList :response-time="100" :min-loading-time="100" />
<FruitList :response-time="1000" :min-loading-time="100" />
fruitList.vue
const { loading: isLoading, result: fruits, load: loadFruitList } = useLoadWithMinimumTime(getFruits, {
minimumTime: minLoadingTime,
})
useLoadWithMinimumTime.ts
import { ref, type Ref } from 'vue'
function promiseWithMinimumTime<T>(promise: Promise<T>, minimumTime: number): Promise<T> {
return new Promise((resolve, reject) => {
const startTime = Date.now()
promise
.then(result => {
const elapsedTime = Date.now() - startTime
const remainingTime = minimumTime - elapsedTime
if (remainingTime > 0) {
setTimeout(() => {
resolve(result)
}, remainingTime)
} else {
resolve(result)
}
})
.catch(error => {
const elapsedTime = Date.now() - startTime
const remainingTime = minimumTime - elapsedTime
if (remainingTime > 0) {
setTimeout(() => {
reject(error)
}, remainingTime)
} else {
reject(error)
}
})
})
}
interface UseLoadWithMinimumTimeOptions {
minimumTime?: number
}
export function useLoadWithMinimumTime<T>(
promiseFn: () => Promise<T>,
options: UseLoadWithMinimumTimeOptions = {},
) {
const { minimumTime = 0 } = options
const loading = ref(false)
const error = ref('')
const result = ref<T>()
async function load() {
loading.value = true
error.value = ''
try {
const promise = promiseFn()
const promiseWithMinTime = promiseWithMinimumTime(promise, minimumTime)
const promiseResult = await promiseWithMinTime
result.value = promiseResult
} catch (err: unknown) {
result.value = undefined
if (err instanceof Error) {
error.value = err.message
} else if (typeof err === 'string') {
error.value = err
} else {
error.value = 'An unknown error occurred.'
}
} finally {
loading.value = false
}
}
return {
loading,
error,
result,
load,
}
}

Conclusion

Every UI should provide feedback at appropriate times, even potentially with animations to aid understanding.
  • Most users are already used to these kinds of UI quirks.
  • Adding extra logic means more stuff to understand and maintain.
  • It might not bring any big visible gains—just some insights or small UX tweaks.

Further Reading