[重构倒数第24天] - You should use Skeleton

前言

该系列是为了让看过Vue官方文件或学过Vue但是却不知道怎麽下手去重构现在有的网站而去规画的系列文章,在这边整理了许多我自己使用Vue重构很多网站的经验分享给读者们。

我们在开发网站的时候常常会遇到说图片或是文字还没有透过 API 回传回来的时候,我们的画面的 UI 高度可能还没有,这时候图片跟文字载入完成後会突然的撑开 UI 的高度,整个 web 页面会有种闪一下的状况,会让看的人有种不舒服的体验感,所以在处理这种资料载入的方法,就延伸出一种叫做 Skeleton 的概念。

vue 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的状态来做判断依据。

我把所有的图片路径全部塞入一个阵列摊平,再透过图片载入的函式把图片全部载入。

现在就是我们完成的样子

vue mike

你会发现到,虽然我的API资料已经回来了,但是我的图片却还没有载入完成,所以这个时候画面上面图片就会比文字晚出来,所以才会造成这样的时间差抖动感觉。

在这边要稍微重点提醒两个重点关键

  1. API 是非同步的,API里面本身会包含图片的路径。

  2. 浏览器载入图片也是非同步的,所以文字比图片出来的快是正常的。

接下来我们要稍微调整一下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;
    } 
}

以下是几个重要的调整

  1. 首先新增了一个 .load 的 class ,然後里面的结构完全跟 .card 一模一样。
  2. 原本要靠内容物撑开的高度,现在全部都设定了预设的高度,也给了预设的背景颜色,让使用者知道这个区块。
  3. 撰写反光渐层以及动画,让画面再载入的时候不是只有单纯的色块。

然後来看 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 。

mike vue

Skeleton 开发的前提

Skeleton 开发说穿了就是把原本需要靠内容物撑开的高度先写好,去替换里面的内容就好,但是这种做法有几个需要注意的

  1. 这不是只有前端需要对工法上面的处理,更应该要在事前设计的时候让设计师把 Skeleton 的概念给考虑进去,假设今天图片是让使用者随便上传的,那就会有宽高的问题,如果不能限制使用者上传的宽高,那就只能我们定义预设宽高度,当今天使用者上传的图片宽高超过或是未达预设宽高时该怎麽处理,也都是在设计师的范畴内,要做的更极端点很多资讯也需要後端提供,例如文字的多寡或是预设的高度等等,所以 Skeleton 的概念是一个会需要团队都有对 Skeleton 有共识的时候,才能完美实现的一个概念。
  2. Skeleton 取代了以往的整个页面的 loading 画面,不需要再让所有图片都载完才可以把画面秀出来,但是不代表整页的 loading 画面不需要,像是很多动态的网页就不适合做 Skeleton,所以 Skeleton 并不是一个通用所有页面的解法,而是需要再依照呈现的方式来决定透过哪种方式来给使用者看。
  3. Skeleton 的开发会增加许多的 code 来处理预设画面的部分,所以很多组件的预设呈现其实是没有办法共用的,所以制作上面还需要考虑是不是全部的 UI 都需要 Skeleton,并非整个页面都会需要 Skeleton。

我在codepen上面有新增了这个范例
有兴趣的朋友可以看看 https://codepen.io/MikeCheng1208/pen/PomyJNa?editors=1010

Mike Vue

那如果对於Vue3不够熟的话呢?

Ps. 购买的时候请登入或注册该平台的会员,然後再使用下面连结进入网站点击「立即购课」,这样才可以让我获得更多的课程分润,还可以帮助我完成更多丰富的内容给各位。

我有开设了一堂专门针对Vue3从零开始教学的课程,如果你觉得不错的话,可以购买我课程来学习
https://hiskio.com/bundles/9WwPNYRpz?s=tc

那如果对於JS基础不熟的朋友,我也有开设JS的入门课程,可以参考这个课程
https://hiskio.com/bundles/b9Rovqy7z?s=tc

订阅Mike的频道享受精彩的教学与分享

Mike 的 Youtube 频道
Mike的medium
MIke 的官方 line 帐号,好友搜寻 @mike_cheng


<<:  Kotlin Android 第2天,从 0 到 ML - Android Studio 开发工具安装及环境设定

>>:  Material UI in React [Day 6] Theme (Globals) & Inputs (Buttons)

Python Callback Function 回呼函式

Callback Function 回呼函式 Callback Function 其实描述的就是一个...

.NET Core第1天_.NET Core 跟其他程序语言框架评比

ASP.NET Core 主要是一个跨平台、高性能之开源Web框架 就高性能这部分 於Web Fra...

Day11 Internet-Protocol

在开发网页服务器之前,必须了解网际网路资料传输的基本背景知识。今天的内容是简单介绍常被忽略的网际网路...

Day 12 跑 Tensorflow Serving Docker 范例

又到了跑范例环节,这次要试着学习 Tensorflow Serving, Tensorflow Se...

D12/ 我要怎麽用动画改变中的资料? - Animations

今天大概会聊到的范围 Animation 上一次有聊到,我们可以透过 Gesture 和 Stat...