大家好!今天我們要聊一聊網頁開發中的一個「秘密武器」——骨架屏(Skeleton Loading)!
有沒有發現,有些網站在內容還沒載入完畢時,已經展示出一個「預先載入」的畫面,讓人覺得一切都很流暢?
這就是骨架屏的魔力!
不僅讓使用者覺得載入更快,還能提升網站的整體體驗感。
準備好了嗎?讓我們一起來解鎖這項技術吧,輕鬆提升你網站的速度感!
骨架屏(Skeleton Screen)是一種在網頁或應用載入過程中常見的過渡效果,它通過展示簡化的佈局來取代傳統的載入圖示或空白頁面。
具體來說,骨架屏的目的在於展示佔位符,這些佔位符模擬最終內容的佈局與結構,讓使用者感覺到頁面正在載入,並即將完成。
骨架屏的實現通常會使用一些灰色、白色或淺色的區塊,有時也會結合閃光加載(Shimmer Loading)效果,透過動畫模擬動態載入的過程,增強使用者的體驗。
提升使用者體驗:骨架屏能讓使用者在等待頁面加載時感到更加安心,減少焦慮。相比於傳統的 loading 圖示,骨架屏讓使用者感覺頁面幾乎已經完成載入,提供一種即將可用的預期感。
減少視覺跳脫:由於骨架屏模擬了即將呈現的內容,當最終內容加載完成時,視覺上不會感覺頁面突然跳動,過渡顯得更加順暢和自然。
保持視覺一致性:即使部分資源尚未完全加載,骨架屏能確保頁面在載入過程中的視覺統一,避免出現空白或錯亂的畫面。
這種過渡技術不僅改善了使用者的等待體驗,還能提升整體的設計感和流暢度,是現代網頁設計中非常實用的技巧。
以下我們將深入探討這次實作的核心技術細節,包含父元件和子元件的結構及行為,並結合 TypeScript 來強化開發過程的型別安全性。
父元件負責傳遞資料至子元件,並控制骨架屏效果的顯示。
首先,我們建立一個包含多筆卡片資訊的 cardInfos
陣列,然後利用 v-for
指令迭代生成每張卡片的骨架屏效果或實際內容。
<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 秒後模擬資料載入完成。
interface
定義了卡片資料的結構,並利用 defineProps
來接收來自父元件傳遞的 card
資料。loading
狀態管理:使用 shallowRef
定義 loading
狀態,並透過 onMounted
在元件掛載後啟動一個 2 秒的定時器,模擬載入過程,然後將 loading
設為 false
,顯示實際內容。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>
在模板中,我們使用 v-if
檢查 loading
狀態,以決定顯示骨架屏還是實際卡片內容。當 loading
為 true
時,會顯示骨架屏佔位符;當 loading
為 false
時,則顯示卡片的具體內容。
骨架屏效果:骨架屏的樣式主要使用 animate-pulse
來實現閃光加載效果,這使得佔位符區塊看起來像是在逐漸加載。
卡片內容顯示:當 loading
為 false
時,我們展示來自父元件的 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
來達成淡入淡出的效果。.fade-enter-active
和 .fade-leave-active
opacity
被設置了 0.5 秒的過渡時間,表示當元素進入或離開時,透明度會在 0.5 秒內從 0 變成 1 或從 1 變成 0,達到平滑的淡入淡出效果。.fade-enter
和 .fade-leave-to
.fade-enter
在元素進入之前生效,設定 opacity: 0
,代表元素在進入前是完全透明的。
.fade-leave-to
在元素即將離開時生效,將 opacity
設為 0,表示元素在離開時會逐漸變為透明。
當資料從骨架屏過渡到實際內容時,我們使用了 transition
元件來包裹顯示內容的區域。
這使得當頁面加載完成時,骨架屏不會突然消失,而是逐漸淡出,與實際內容的淡入過程相吻合,實現了更自然、流暢絲滑的感覺。
在這個子元件中,我們還為“了解更多”的按鈕添加了一個自定義的 bounce-x
動畫效果,透過 keyframes
和 transform
屬性,讓箭頭圖示左右擺動,吸引使用者來點擊文章,提升互動性與點閱率。
<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>
這次的骨架屏實作展示了如何在 Vue.js 中巧妙運用骨架屏來提升頁面加載體驗。
骨架屏不僅能減少頁面跳動,還透過動畫效果讓使用者更清楚頁面的加載進度。
我們也展示了如何結合 TypeScript 定義型別,讓元件開發更加結構化且易讀。
核心技術要點:
card
資料的結構,確保資料的一致性和可靠性。animate-pulse
和自定義 CSS 動畫實現動態加載效果。v-if
指令和 onMounted
鉤子來管理骨架屏與真實內容的切換。這樣的技術不僅提升了使用者體驗,還讓網頁加載更加平滑,特別是在圖片或複雜內容加載時效果更為明顯。
別讓等待成為焦慮,讓骨架屏為你的載入過程添點美好,讓每個細節都充滿期待。✨✨✨٩(๑❛ᴗ❛๑)۶