JavaScriptでローディング表示のチラつきを防ぐ方法

JavaScriptでローディング表示のチラつき防止

JavaScriptでデータの取得を行う際、ローディング表示(スピナーなど)をそのまま実装すると、通信速度が速すぎる場合に「一瞬だけチラついて消える」という現象が起こります。

このチラつきはユーザーに違和感を与え、UX(ユーザー体験)を低下させる原因になります。

チラつきが起きる原因と対策

通信処理が数十ミリ秒などの超高速で完了した場合、ローディング画面が一瞬(数フレーム程度)だけ表示されてすぐに消えます。

ユーザーの目には「画面がガタついた」「一瞬何か崩れた」ように見えてしまい、ストレスや違和感を与えてしまいます。

これを防ぐには「500ms以上かかった場合のみローディングを表示する」という制御を行います。

JavaScript
if (status === 'pending') {
  loadingTimer = setTimeout(() => {
    loadingText.style.display = 'block'
  }, 500)
} else {
  if (loadingTimer) {
    clearTimeout(loadingTimer)
    loadingTimer = null
  }
  loadingText.style.display = 'none'
}

JavaScriptでローディング表示のチラつきを防ぐサンプル

Next.js(React)ならuseEffect、Nuxt(Vue)ならwatchでstatusを監視することで、同様の処理ができます。

app.vue
<script setup lang="ts">
type Product = {
  id: number
  title: string
  thumbnail: string
}

const productId = ref<number | null>(null)
const { data, status, execute } = await useFetch<Product>(
  () => `https://dummyjson.com/products/${productId.value}`,
  {
    pick: ['id', 'title', 'thumbnail'],
    key: 'product',
    enabled: () => productId.value !== null,
    immediate: false,
    watch: false,
  }
)

// pendingが500ms以上続いたときだけloadingを表示する
const showLoading = ref(false)
let loadingTimer: ReturnType<typeof setTimeout> | null = null

watch(status, (newStatus) => {
  if (newStatus === 'pending') {
    loadingTimer = setTimeout(() => {
      showLoading.value = true
    }, 500)
  } else {
    if (loadingTimer) {
      clearTimeout(loadingTimer)
      loadingTimer = null
    }
    showLoading.value = false
  }
})
</script>

<template>
  <h1>商品ページ</h1>
  <h2>productId: {{ productId }}</h2>
  <select v-model="productId">
    <option :value="null">--</option>
    <option :value="1">1</option>
    <option :value="2">2</option>
    <option :value="3">3</option>
  </select>
  <button :disabled="status === 'pending'" @click="() => execute()">
    商品データ取得
  </button>
  <p v-if="showLoading">loading...</p>
  <p v-else-if="status === 'error'">error!</p>
  <p v-else-if="status === 'success' && data">
    {{ data.title }}<br />
    <img :src="data.thumbnail" :alt="data.title" width="200" height="200" />
  </p>
</template>

Nuxt(Vue)でローディング表示のチラつきを防ぐサンプル

もしWebサイトでチラつきが気になるので改善してほしいという要望があった場合は、記事に記載した方法で対処することを推奨します。

逆に以下のように、わざと500ms遅延させて表示させるような方法は非推奨なので使わないでください。

JavaScript
const [data] = await Promise.all([
  fetch('/api/data').then((res) => res.json()),
  await new Promise((resolve) => setTimeout(resolve, 500))
])