iT邦幫忙

2024 iThome 鐵人賽

DAY 23
0
Modern Web

創意前端設計:用 Vue.js 打造 30 個互動實用功能系列 第 23

Day23 Vue.js 動效分類實戰 (14) 骨架屏特輯 - 讓你的網站載入像閃電般快速

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20241007/20124462bGeRd9rykl.jpg


骨架屏黑科技,提升載入體驗

大家好!今天我們要聊一聊網頁開發中的一個「秘密武器」——骨架屏(Skeleton Loading)!
有沒有發現,有些網站在內容還沒載入完畢時,已經展示出一個「預先載入」的畫面,讓人覺得一切都很流暢?
這就是骨架屏的魔力!

不僅讓使用者覺得載入更快,還能提升網站的整體體驗感。
準備好了嗎?讓我們一起來解鎖這項技術吧,輕鬆提升你網站的速度感!


img

Skeleton Loading 與 Shimmer Loading

骨架屏(Skeleton Screen)是一種在網頁或應用載入過程中常見的過渡效果,它通過展示簡化的佈局來取代傳統的載入圖示或空白頁面。

具體來說,骨架屏的目的在於展示佔位符,這些佔位符模擬最終內容的佈局與結構,讓使用者感覺到頁面正在載入,並即將完成。

骨架屏的實現通常會使用一些灰色、白色或淺色的區塊,有時也會結合閃光加載(Shimmer Loading)效果,透過動畫模擬動態載入的過程,增強使用者的體驗。

骨架屏的優勢

https://ithelp.ithome.com.tw/upload/images/20241007/20124462TIfNssL8XV.png

  • 提升使用者體驗:骨架屏能讓使用者在等待頁面加載時感到更加安心,減少焦慮。相比於傳統的 loading 圖示,骨架屏讓使用者感覺頁面幾乎已經完成載入,提供一種即將可用的預期感。

  • 減少視覺跳脫:由於骨架屏模擬了即將呈現的內容,當最終內容加載完成時,視覺上不會感覺頁面突然跳動,過渡顯得更加順暢和自然。

  • 保持視覺一致性:即使部分資源尚未完全加載,骨架屏能確保頁面在載入過程中的視覺統一,避免出現空白或錯亂的畫面。

這種過渡技術不僅改善了使用者的等待體驗,還能提升整體的設計感和流暢度,是現代網頁設計中非常實用的技巧。
以下我們將深入探討這次實作的核心技術細節,包含父元件和子元件的結構及行為,並結合 TypeScript 來強化開發過程的型別安全性。


父元件實作

父元件負責傳遞資料至子元件,並控制骨架屏效果的顯示。
首先,我們建立一個包含多筆卡片資訊的 cardInfos 陣列,然後利用 v-for 指令迭代生成每張卡片的骨架屏效果或實際內容。

https://ithelp.ithome.com.tw/upload/images/20241007/20124462FN8nADMIwJ.png

  • 程式碼
<script lang="ts" setup>
import CardSkeleton from "../components/CardSkeleton.vue";

const cardInfos = [
  {
    category: '流星',
    title: '星空奇觀',
    content: '美麗的夜空中點綴著閃爍的星星,像流星雨般墜落。',
    imgSrc: 'https://imgs.699pic.com/images/600/561/214.jpg!list1x.v2'
},
  {
    category: '山水',
    title: '壯麗山景',
    content: '連綿不斷的山脈與蔚藍的天空相映成趣,勾勒出壯麗的自然景色。',
    imgSrc: 'https://images.unsplash.com/photo-1506748686214-e9df14d4d9d0?ixlib=rb-1.2.1&auto=format&fit=crop&w=1050&q=80',
  },
  {
    category: '城市',
    title: '繁華都市',
    content: '現代化的城市景觀充滿了活力與創新,夜晚的燈光點亮了整座城市。',
    imgSrc: 'https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQdXyCfmDQwVIKQKZipPFJ1o3yX-uQfUSIFuA&s',
  }
];

</script>

<section class="text-gray-700 body-font">
  <div class="container px-5 py-24 mx-auto">
    <div class="flex flex-wrap -m-4"> 
      <CardSkeleton 
        v-for="(card, index) in cardInfos" 
        :key="index" 
        :card="card" 
      />
    </div>
  </div>
</section>

  • 核心技術細節

  • v-for 指令:用來迭代 cardInfos 陣列,並為每個卡片生成一個 CardSkeleton 子元件。

  • :key 綁定:為每個生成的子元件指定唯一的 key,以便 Vue 能有效追蹤每個元素的狀態,增強渲染性能。

  • :card 傳遞參數:使用 props 將卡片的具體資料(如類別、標題、內容和圖片 URL)傳遞給子元件,這使得每個子元件可以渲染不同的內容。


子元件 CardSkeleton 的實作詳解

CardSkeleton 子元件負責處理資料的顯示邏輯及骨架屏效果。在這裡,當資料還未完全載入時,將顯示骨架屏,並在 2 秒後模擬資料載入完成。

  • 主要技術點
  1. TypeScript 的型別定義:我們首先透過 interface 定義了卡片資料的結構,並利用 defineProps 來接收來自父元件傳遞的 card 資料。
  2. loading 狀態管理:使用 shallowRef 定義 loading 狀態,並透過 onMounted 在元件掛載後啟動一個 2 秒的定時器,模擬載入過程,然後將 loading 設為 false,顯示實際內容。
  3. v-if 指令:根據 loading 狀態,動態顯示骨架屏或實際內容。
<script lang="ts" setup>
import { shallowRef, onMounted } from "vue";

interface Card {
  category: string;
  title: string;
  content: string;
  imgSrc: string;
}

// 接收父元件傳入的 card 資料
defineProps<{ card: Card }>();

const loading = shallowRef<boolean>(true);

onMounted(() => {
  setTimeout(() => {
    loading.value = false;
  }, 2000); // 模擬 2 秒後資料讀取完成
});
</script>


子元件卡片的模板結構

https://ithelp.ithome.com.tw/upload/images/20241007/20124462NXhza9Ewxy.png

在模板中,我們使用 v-if 檢查 loading 狀態,以決定顯示骨架屏還是實際卡片內容。當 loadingtrue 時,會顯示骨架屏佔位符;當 loadingfalse 時,則顯示卡片的具體內容。

  • 骨架屏效果:骨架屏的樣式主要使用 animate-pulse 來實現閃光加載效果,這使得佔位符區塊看起來像是在逐漸加載。

  • 卡片內容顯示:當 loadingfalse 時,我們展示來自父元件的 card 資料,包括圖片、類別、標題和內容等。

  • 子元件 template 程式碼


<template>
<transition name="fade" mode="out-in">
<!-- 加載中時渲染骨架屏 -->
  <div v-if="!loading" class="p-4 md:w-1/3">
    <div class="h-full border-2 border-gray-200 rounded-lg overflow-hidden cursor-pointer">
      <img
        class="lg:h-48 md:h-36 w-full object-cover object-center"
        :src="card.imgSrc"
        alt="部落格"
      />
      <div class="p-6" bg-white>
        <h2 class="tracking-widest text-xs title-font font-medium text-gray-500 mb-1">
            {{ card.category }}
        </h2>
        <h1 class="title-font text-lg font-medium text-gray-900 mb-3">   {{ card.title }}</h1>
        <p class="leading-relaxed mb-3">{{ card.content }}</p>
        <div class="flex items-center flex-wrap">
          <a class="text-indigo-500 inline-flex items-center md:mb-2 lg:mb-0"
            >了解更多
            <svg
              class="w-4 h-4 ml-2 animate-bounce-x"
              viewBox="0 0 24 24"
              stroke="currentColor"
              stroke-width="2"
              fill="none"
              stroke-linecap="round"
              stroke-linejoin="round"
            >
              <path d="M5 12h14"></path>
              <path d="M12 5l7 7-7 7"></path>
            </svg>
          </a>
          <span
            class="text-gray-600 mr-3 inline-flex items-center lg:ml-auto md:ml-0 ml-auto leading-none text-sm pr-3 py-1 border-r-2 border-gray-300"
          >
            <svg
              class="w-4 h-4 mr-1"
              stroke="currentColor"
              stroke-width="2"
              fill="none"
              stroke-linecap="round"
              stroke-linejoin="round"
              viewBox="0 0 24 24"
            >
              <path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"></path>
              <circle cx="12" cy="12" r="3"></circle></svg
            >1.2K
          </span>
          <span class="text-gray-600 inline-flex items-center leading-none text-sm">
            <svg
              class="w-4 h-4 mr-1"
              stroke="currentColor"
              stroke-width="2"
              fill="none"
              stroke-linecap="round"
              stroke-linejoin="round"
              viewBox="0 0 24 24"
            >
              <path
                d="M21 11.5a8.38 8.38 0 01-.9 3.8 8.5 8.5 0 01-7.6 4.7 8.38 8.38 0 01-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 01-.9-3.8 8.5 8.5 0 014.7-7.6 8.38 8.38 0 013.8-.9h.5a8.48 8.48 0 018 8v.5z"
              ></path></svg
            >6
          </span>
        </div>
      </div>
    </div>
  </div>


<!-- 加載完成時渲染內容 -->
  <div v-else class="p-4 md:w-1/3">
    <div class="h-full border-2 border-gray-200 rounded-lg overflow-hidden">
      <div class="lg:h-48 bg-gray-400 md:h-36 w-full object-cover object-center"></div>
      <div class="p-6">
        <h2 class="bg-gray-400 animate-pulse h-4 w-1/4 mb-2"></h2>
        <h1 class="w-1/2 mb-4 h-6 animate-pulse bg-gray-500"></h1>
        <p class="leading-relaxed mb-3 w-full h-3 animate-pulse bg-gray-400"></p>
        <p class="leading-relaxed mb-3 w-2/3 h-3 animate-pulse bg-gray-400"></p>
        <p class="leading-relaxed mb-3 w-1/2 h-3 animate-pulse bg-gray-400"></p>
        <div class="flex items-center flex-wrap">
          <a
            class="bg-indigo-300 h-4 animate-pulse mt-2 w-32 inline-flex items-center md:mb-2 lg:mb-0"
          >
          </a>
          <span
            class="bg-indigo-300 w-16 mt-2 h-4 animate-pulse mr-3 px-2 inline-flex items-center ml-auto leading-none text-sm pr-5 py-1"
          >
          </span>
        </div>
      </div>
    </div>
  </div>
</transition>
</template>
  • 還有之前學到的過渡效果
  • 讓我們動效更加細膩絲滑
.fade-enter-active, .fade-leave-active {
  transition: opacity 0.5s;
}

.fade-enter, .fade-leave-to {
  opacity: 0;
}

transition 進出過渡

  • transition 元件提供了一個簡單的方法來控制元素進入和離開的過渡效果。在這裡,我們使用 opacity 來達成淡入淡出的效果。

2. .fade-enter-active.fade-leave-active

  • 這兩個 class 定義了元素進入和離開過程中的過渡行為。這裡的 opacity 被設置了 0.5 秒的過渡時間,表示當元素進入或離開時,透明度會在 0.5 秒內從 0 變成 1 或從 1 變成 0,達到平滑的淡入淡出效果。

3. .fade-enter.fade-leave-to

  • .fade-enter 在元素進入之前生效,設定 opacity: 0,代表元素在進入前是完全透明的。

  • .fade-leave-to 在元素即將離開時生效,將 opacity 設為 0,表示元素在離開時會逐漸變為透明。

  • 當資料從骨架屏過渡到實際內容時,我們使用了 transition 元件來包裹顯示內容的區域。

  • 這使得當頁面加載完成時,骨架屏不會突然消失,而是逐漸淡出,與實際內容的淡入過程相吻合,實現了更自然、流暢絲滑的感覺。


子元件可愛的箭頭動效

在這個子元件中,我們還為“了解更多”的按鈕添加了一個自定義的 bounce-x 動畫效果,透過 keyframestransform 屬性,讓箭頭圖示左右擺動,吸引使用者來點擊文章,提升互動性與點閱率。

img

  • 動效部分

<style scoped>
@layer utilities {
  @keyframes bounce-x {
    0%, 100% {
      transform: translateX(0);
      animation-timing-function: cubic-bezier(0.8, 0, 1, 1);
    }
    50% {
      transform: translateX(25%);
      animation-timing-function: cubic-bezier(0, 0, 0.2, 1);
    }
  }

  .animate-bounce-x {
    animation: bounce-x 1s infinite;
  }
}
</style>

總結

img

這次的骨架屏實作展示了如何在 Vue.js 中巧妙運用骨架屏來提升頁面加載體驗。
骨架屏不僅能減少頁面跳動,還透過動畫效果讓使用者更清楚頁面的加載進度。
我們也展示了如何結合 TypeScript 定義型別,讓元件開發更加結構化且易讀。

核心技術要點:

  • TypeScript 型別安全性:通過定義並強制 card 資料的結構,確保資料的一致性和可靠性。
  • 骨架屏動畫效果:使用 animate-pulse 和自定義 CSS 動畫實現動態加載效果。
  • 過渡控制:透過 Vue 的 v-if 指令和 onMounted 鉤子來管理骨架屏與真實內容的切換。

這樣的技術不僅提升了使用者體驗,還讓網頁加載更加平滑,特別是在圖片或複雜內容加載時效果更為明顯。

別讓等待成為焦慮,讓骨架屏為你的載入過程添點美好,讓每個細節都充滿期待。✨✨✨٩(๑❛ᴗ❛๑)۶


上一篇
Day22 Vue.js 動效分類實戰 (13) 極致表單特輯 - 掌控每次提交的反饋魔力
下一篇
Day24 Vue.js 動效分類實戰 (15) SVG特輯 - 實現旋轉華麗的台灣國旗動效,簡單又帥氣
系列文
創意前端設計:用 Vue.js 打造 30 個互動實用功能30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言