【Day25】反馈元件 - Skeleton

元件介绍

Skeleton 是一个骨架载入元件(Skeleton Screen Loading),跟 Spin 不同的是,Skeleton 帮助我们在页面载入完成之前可以先看到一个描绘当前页面大致框架的样式,载入完成之後,原本骨架的地方将被真实的资料给替换。

在资料载入之前,先用色块来代替文字和图片,将页面架构和资料位置呈现在使用者眼前,可以让使用者产生一种「内容即将要出现」的感觉,而非将注意力全放在「等待时间」上。因此使用者体验上会比较好,而且也可以减少 loading icon 突然变成资料区块的不连续突兀感。适合使用在整个区块需要载入的状况。

参考设计 & 属性分析

为了达到不同的效果,各家的做法也不一,因此以下分享几个在网路上人家分享的做法,不一定尽善尽美,但觉得也是看到了不同的思维可以学习。

无动画纯色块

这部分是最单纯,就是用色块勾勒出页面显示的架构。

const SkeletonWrapper = styled.div`
  display: flex;
  align-items: center;
  & > *:not(:first-child) {
    margin-left: 16px;
  }
`;

const TextLineWrapper = styled.div`
  & > *:not(:first-child) {
    margin-top: 12px;
  }
`;

const TextLine = styled.div`
  background: #EEE;
  height: 12px;
`;

const Avatar = styled.div`
  width: 50px;
  height: 50px;
  background: #EEE;
`;


const Skeleton = () => (
  <SkeletonWrapper>
    <Avatar />
    <TextLineWrapper>
      <TextLine style={{ width: 300 }} />
      <TextLine style={{ width: 230 }} />
    </TextLineWrapper>
  </SkeletonWrapper>
);

闪烁色块

因为前面色块真的太单纯,可能画面会呆呆的,不太知道画面是有在载入还是当掉,可以可以简单加一些动画,直接用颜色深浅的变化来做。

我目前做的范例是跟先前一样,只是加入一个闪烁的动画,整体的感觉就很不一样。我用的动画只有单纯改变 opacity,然後动画的设定是用 alternate-reverse,可以像是乒乓球一样来回播放,这样看起来比较不会卡顿,最後是 infinite 让他无限轮播。

附注:我用的 gif 动画跑得不是很顺,读者可以上我的 storybook 看。

const flash = keyframes`
  from {
    opacity: 0.3;
  }
  to {
    opacity: 1;
  }
`;

const TextLine = styled.div`
  background: #EEE;
  height: 12px;
  animation: ${flash} 0.8s ease-in-out alternate-reverse infinite;
`;

const Avatar = styled.div`
  width: 50px;
  height: 50px;
  background: #EEE;
  animation: ${flash} 0.8s ease-in-out alternate-reverse infinite;
`;

背景光晕移动动画

Antd 也是比较接近下面这种动画,就是我们可以看到一个阴影或是光晕的东西在前面滑过,有点像是光源移动的感觉。


https://medium.com/d-d-mag/protopie-实验室-skeleton-loading-63d3a939a962

其实原理如这张动画:

做法的概念简单来说,就是我们要制造的一个剪层色块在背景从左到右滑动,然後不间断地轮播就可以了,然後渐层超出背景的地方,就用 overflow: hidden; 处理掉。

下面这边就是我的作法:

const slide = keyframes`
  from {
    left: -150%;
  }
  to {
    left: 100%;
  }
`;

const StyledBackgroundSlide = styled.div`
  width: 12px;
  height: 12px;
  background: #EEE;
  position: relative;
  overflow: hidden;
  &:before {
    content: '';
    position: absolute;
    height: 100%;
    width: 80px;
    top: 0px;
    background: linear-gradient(to right, transparent 0%, #FFFFFF99 50%, transparent 100%);
    animation: ${slide} 1s cubic-bezier(0.4, 0.0, 0.2, 1) infinite;
    box-shadow: 0 4px 10px 0 #FFFFFF33;
  }
`;

const BackgroundSlide = (props) => (
  <StyledBackgroundSlide {...props} />
);

我的背景是用 <div />,然後渐层色块我用一个伪元素 :before 来做,主要是利用 linear-gradient 来制造渐层的背景,最後加上一点 box-shadow 来让这个渐层更延伸,好像有点光晕感,看起来比较自然一些。

介面设计

属性 说明 类型 默认值
variant 变化模式 colorBlock, flash, slide slide

元件实作

要元件实作的时候发现我们上面在分析解释就已经讲完了 XD

所以上述三种不同动画样式我乾脆就把他包做一个元件,然後用 variant 来决定要呈现哪一种动画,像是下面这样:

<Skeleton variant="slide" />

然後根据我们不同的页面架构,我们再来勾勒出它的形状,例如像上面一个 Avatar 搭配两行字的架构,我们就能够用下面的方式来做:

import Skeleton from '../components/Skeleton';

const Avatar = ({ style, ...props }) => (
  <Skeleton style={{ width: 50, height: 50, ...style }} {...props} />
);

const TextLine = ({ style, ...props }) => (
  <Skeleton style={{ width: 50, height: 12, ...style }} {...props} />
);

const SkeletonDemo = ({ variant }) => (
  <SkeletonWrapper>
    <Avatar variant={variant} />
    <TextLineWrapper>
      <TextLine variant={variant} style={{ width: 300 }} />
      <TextLine variant={variant} style={{ width: 230 }} />
    </TextLineWrapper>
  </SkeletonWrapper>
);

所以我们这边准备的是一个最基础单元的 Skeleton,那如果我们需要一篇文章的 Skeleton,或是一张卡片的 Skeleton,那我们再用这个小单元来组成就可以了,那如果很重复使用这些大单元,我们就能够再额外把他包成一个元件,例如:<ArticleSkeleton />, <CardSkeleton />...等等。


Skeleton 元件原始码:
Source code

Storybook:
Skeleton

参考

https://medium.com/d-d-mag/protopie-实验室-skeleton-loading-63d3a939a962

https://w3c.hexschool.com/blog/24db18f8

https://betterprogramming.pub/the-what-why-and-how-of-using-a-skeleton-loading-screen-e68809d7f702

https://medium.com/tiffrrr/css-使用background-animation-制作skeleton-loading-screen-f68fb307525c


<<:  自动化 End-End 测试 Nightwatch.js 与 BrowserStack

>>:  DAY23 - 我的网站要分析!网站分析工具的选择和态度(1)

成为工具人应有的工具包-26 ShellExView

ShellExView 今天来认识的小工具是看 Shell 的(猜测 ShellExView She...

补充: 建立 Todo list 画面

发现昨天的介绍中漏掉新增 Todo 的画面是怎麽来的,补充一下。 安装 React Material...

根据 NIST SP 800-204通讯 (Communication) 是对基於微服务的应用程序是最为独有的

以下是 NIST SP 800-204 的摘录: 典型的基於微服务的应用程序的部署堆栈中存在六层,如...

予焦啦!scratch 控制暂存器

本节是以 Golang 上游 8854368cb076ea9a2b71c8b3c8f675a8e1...

Day30 语法改革!零基础新手也能读懂的JS - 最终回!

终於来到我们第三十天,在最後的第三十天,我想说几句完赛感言! 其实无法想像我居然有这个毅力可以撑过一...