跳至主要内容

[javascript] 使用 Skeleton 增加使用者體驗

假設網頁需要在一開始載入過大的內容,導致網頁畫面渲染過久,使內容無法第一時間呈現給使用者,這就會降低使用者體驗,所以許多網頁在載入資源時會使用 Skeleton,避免使用者看到的畫面是空白的網頁。

Skeleton 的效果如下圖:

Images

今天的完成品如下圖所示:

製作 Skeleton

首先先定義最基本的 HTML 結構,這邊會使用 template 標籤先自訂一個樣板和一個div標籤,該 div 標籤會拷貝 template 標籤內的結構。

製作 Skeleton 其實就只是將原本需載入內容的容器加上載入的動畫而已,以下 HTML 結構有加上 skeleton 的都是待會會在 css 設置動畫的 class,其餘 css 的部分就不多贅述。

index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Skeleton</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="grid"></div>

<template id="card-template">
<div class="card">
<div class="header">
<img
class="header-img skeleton"
src="https://source.unsplash.com/100x100/?nature"
/>
<div class="title" data-title>
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text"></div>
</div>
</div>
<div data-body>
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text"></div>
</div>
</div>
</template>
<script src="index.js"></script>
</body>
</html>

首先先將 skeleton 的動畫定義出來

  • 1 - 4 行:將 skeleton 透明度設置為 0.7,這麼做的原因是因為要是 skeleton 跟之後要載入的內容背景顏色相差過多,透明度能有效降低色差效果,並且將 skeleton 的載入動畫設定為無限播放 (infinite) 且當動畫結束時,會從動畫結束的地方在回到動畫開始的地方 (alternate),簡單來說就是反向播放。
  • 6 - 10 行:將要載入的文字內容加上間距和圓弧效果。
  • 13 - 15 行:將最後一段文字和其他文字的寬度大小調整,以此達到視差效果。
  • 18 - 26 行:skeleton 動畫效果,0% ~ 100%時將背景顏色進行更改。
style.css
.skeleton {
opacity: 0.7;
animation: skeleton-loading 1s linear infinite alternate;
}

.skeleton-text {
width: 100%;
height: 0.5rem;
margin-bottom: 0.25rem;
border-radius: 0.125rem;
}

.skeleton-text:last-child {
margin-bottom: 0;
width: 80%;
}

@keyframes skeleton-loading {
0% {
background-color: hsl(200, 20%, 70%);
}

100% {
background-color: hsl(200, 20%, 95%);
}
}

/* ------------以上為skeleton動畫的部分------------ */

.grid {
display: grid;
gap: 1rem;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
padding: 1rem;
}

.card {
background-color: blue;
box-shadow: rgba(0, 0, 0, 0.1) 0px 4px 6px -1px, rgba(0, 0, 0, 0.06) 0px 2px 4px -1px;
padding: 16px;
border-radius: 4px;
}

.header {
margin-bottom: 1rem;
display: flex;
align-items: center;
}

.header-img {
width: 50px;
height: 50px;
object-fit: cover;
border-radius: 100%;
margin-right: 1rem;
flex-shrink: 0;
}

.title {
font-weight: bold;
font-size: 1.25rem;
text-transform: capitalize;
word-wrap: none;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
flex-grow: 1;
}

載入內容

接著來到載入內容的部分,這邊會使用 fetch 來取得文章 API,並將文章內容載入我們的畫面

API 網址為:https://jsonplaceholder.typicode.com/posts

  • 1 - 2 行:抓取 DOM 節點。

  • 3 - 5 行:因為是使用template標籤的關係,我們可以將template標籤內的結構拷貝,所以程式碼的意思是,將template內的結構拷貝到grid(class),這邊for迴圈的i<2意思是畫面剛載入的時候,有 2 個 skeleton 的結構顯示在畫面上。

  • 6 - 16 行:利用 fetch 取得外部 API 資源,並將取得的內容顯示在網頁上,這邊一樣是拷貝template結構並渲染到grid(class),如此一來渲染出來的內容都有著 skeleton 的結構,有著 skeleton 的結構代表在載入內容時就不會讓使用者看到空白的畫面。

index.js
const grid = document.querySelector(".grid");
const cardTemplate = document.querySelector("#card-template");
for (let i = 0; i < 2; i++) {
grid.append(cardTemplate.content.cloneNode(true));
}
fetch("https://jsonplaceholder.typicode.com/posts?_limit=2")
.then((res) => res.json())
.then((posts) => {
grid.innerHTML = "";
posts.forEach((post) => {
const div = cardTemplate.content.cloneNode(true);
div.querySelector("[data-title]").textContent = post.title;
div.querySelector("[data-body]").textContent = post.body;
grid.append(div);
});
});