該系列是為了讓看過Vue官方文件或學過Vue但是卻不知道怎麼下手去重構現在有的網站而去規畫的系列文章,在這邊整理了許多我自己使用Vue重構很多網站的經驗分享給讀者們。
我們在開發網站的時候常常會遇到說圖片或是文字還沒有透過 API 回傳回來的時候,我們的畫面的 UI 高度可能還沒有,這時候圖片跟文字載入完成後會突然的撐開 UI 的高度,整個 web 頁面會有種閃一下的狀況,會讓看的人有種不舒服的體驗感,所以在處理這種資料載入的方法,就延伸出一種叫做 Skeleton 的概念。
Skeleton 顧名思義就是骨架,我們可以看到上面範例左邊的卡片當資料載入進來的時候,先把文字給放上去,然後等瀏覽器把圖片給load完成之後,在把圖片給render出來,這時候我們的DOM因為圖片的關係,然後高度就被撐開來了,這樣子的呈現方式如果你的網站設計結構複雜,而且很吃圖片的話,其實很容易造成畫面因為DOM撐開來的閃動
所以為了解決這個問題,我們可以先把因為非同步所載入的圖片或是文字的範圍先給他定義出來,將高度或是寬度定義好,以減少之後圖片跟文字載入後撐開的閃動。
在這邊我就簡單的來帶大家來看一下 Skeleton 該如何實作首先我們先來定義一下html
html
<div class="card" v-for="item in DataValue" :key="item.id">
<header>
<div class="photo">
<img :src="item.avatar">
</div>
<p>{{item.username}}</p>
</header>
<p>{{item.text}}</p>
<main>
<img :src="item.content">
</main>
</div>
這邊我將整個卡片的架構先給他訂出來,包含卡片的使用者圖片,還有他的名字以及圖片
CSS
.card {
width: 300px;
height: auto;
border-radius: 10px;
overflow: hidden;
background-color: #fff;
box-shadow: 0 1px 5px rgba(0, 0, 0, 0.2);
margin-bottom: 50px;
> p {
font-size: 14px;
padding: 0 10px;
margin-bottom: 10px;
}
header {
width: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
padding: 10px;
> div {
margin-right: 10px;
> img {
border-radius: 50%;
}
}
> p {
font-size: 13px;
}
}
img {
opacity: 1;
transition: opacity .3s;
}
}
再來看他的css會發現我們現在的寫法是透過內容來撐開dom的高度,這是很常見的做法。
js
import { ref, onMounted } from "vue"
export default {
setup() {
const DataValue = ref([]);
const isLoad = ref(true);
const LoadImg = (imgUrl) => {
const imgArr = [...imgUrl];
let i = 0;
imgArr.forEach(src=> {
const img = new Image();
img.src = src;
img.onload = () => {
i += 1;
if(i === imgArr.length){
isLoad.value = false;
}
}
})
}
onMounted(()=> {
fetch('https://test.api/api/card')
.then(res => res.json()).then(res => {
DataValue.value = res;
const imgArr = res.map(item=> [item.avatar, item.content]);
LoadImg([].concat(...imgArr));
});
})
return {
isLoad,
DataValue,
};
},
}
在這邊我就透過 fetch 去打API來拿到我卡片的資料,然後寫了一個判斷圖片載入完成沒有的函式去判斷API給我的圖片路徑是否都載入完成了,然後給一個isLoad的狀態來做判斷依據。
我把所有的圖片路徑全部塞入一個陣列攤平,再透過圖片載入的函式把圖片全部載入。
現在就是我們完成的樣子
你會發現到,雖然我的API資料已經回來了,但是我的圖片卻還沒有載入完成,所以這個時候畫面上面圖片就會比文字晚出來,所以才會造成這樣的時間差抖動感覺。
在這邊要稍微重點提醒兩個重點關鍵
API 是非同步的,API裡面本身會包含圖片的路徑。
瀏覽器載入圖片也是非同步的,所以文字比圖片出來的快是正常的。
接下來我們要稍微調整一下CSS的部分
.card {
// 跟上面一樣的code
}
.load {
width: 300px;
height: 380px;
border-radius: 10px;
overflow: hidden;
background-color: #fff;
> header {
width: 100%;
display: flex;
justify-content: flex-start;
align-items: center;
padding: 10px;
> div {
width: 30px;
height: 30px;
border-radius: 50px;
margin-right: 10px;
background-color: #ededed;
> img {
border-radius: 50%;
}
}
> p {
font-size: 13px;
display: block;
width: 40%;
height: 18px;
background-color: #ededed;
}
}
> p {
font-size: 14px;
margin: 0 10px 10px 10px;
display: block;
width: 70%;
height: 18px;
background-color: #ededed;
}
> main {
width: 100%;
height: 300px;
background-color: #ededed;
}
img {
opacity: 0;
}
}
@keyframes loading {
to{
background-position-x: -20%;
}
}
.load {
.photo, p, main {
background: linear-gradient(
100deg,
rgba(256, 256, 256, 0) 30%,
rgba(256, 256, 256, 0.5) 50%,
rgba(256, 256, 256, 0) 30%)
#ededed;
background-size: 200% 100%;
background-position-x: 180%;
animation: 2s loading ease-in-out infinite;
}
}
以下是幾個重要的調整
.load
的 class ,然後裡面的結構完全跟 .card
一模一樣。然後來看 html 的部分
<div
:class="['card', {load: isLoad}]"
v-for="item in DataValue"
:key="item.id"
>
<header>
<div class="photo">
<img :src="item.avatar">
</div>
<p>{{ isLoad ? "" : item.username }}</p>
</header>
<p>{{ isLoad ? "" : item.text }}</p>
<main>
<img :src="item.content">
</main>
</div>
在這邊原本的 card class上面多加了一個 .load 的 class,也就是說一開始的時候我們其實是透過 .load 去覆蓋 .card 的 css,然後當我們圖片都載入完成的時後,透過 isLoad 來拿掉 .load 在切換回 .card 的 css,這樣就可以達到 Skeleton 的效果,在沒有抖動的情況下完成畫面的 render 。
Skeleton 開發說穿了就是把原本需要靠內容物撐開的高度先寫好,去替換裡面的內容就好,但是這種做法有幾個需要注意的
我在codepen上面有新增了這個範例
有興趣的朋友可以看看 https://codepen.io/MikeCheng1208/pen/PomyJNa?editors=1010
Ps. 購買的時候請登入或註冊該平台的會員,然後再使用下面連結進入網站點擊「立即購課」,這樣才可以讓我獲得更多的課程分潤,還可以幫助我完成更多豐富的內容給各位。
我有開設了一堂專門針對Vue3從零開始教學的課程,如果你覺得不錯的話,可以購買我課程來學習
https://hiskio.com/bundles/9WwPNYRpz?s=tc
那如果對於JS基礎不熟的朋友,我也有開設JS的入門課程,可以參考這個課程
https://hiskio.com/bundles/b9Rovqy7z?s=tc
Mike 的 Youtube 頻道
Mike的medium
MIke 的官方 line 帳號,好友搜尋 @mike_cheng