跳至主要内容

[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);    });  });