iT邦幫忙

2021 iThome 鐵人賽

DAY 24
1

元件介紹

Spin 是一個載入狀態元件,當頁面正在處理非同步行為,或需要讓用戶等待的作業時,用來顯示以緩解用戶等待的焦慮。

參考設計 & 屬性分析

一個簡單的 loading 狀態,只需要拿一個適合的 icon 來旋轉就可以了

Spin 元件雖然相對單純,但是也是有一些做法可以讓我們使用起來更為方便。

內容加載中

Antd 讓 Spin 也能夠擁有 children 元件,這樣的做法可以讓載入的同時也能夠看到內容。

<Spin spinning={this.state.spinning}>
  {children}
</Spin>

假設一個情境是我們需要編輯某個資料 table 或是留言板,每做一個小動作就要打一次 API ,每次打 API 就會整頁白掉變成 loading 狀態,載入完之後再重新顯示資料,如果這些操作很頻繁的話,那這樣的使用者體驗必定很不好。

因此若這一頁的內容正在載入中,但是又不想要整個區塊換成載入狀態,或許用這種內容加載的方式也是一種選擇。

組建大小

透過一個 props 來決定 Spin 的大小,也是很基本卻很重要的性質之一,畢竟把 Spin 放在一顆按鈕裡面,跟把 Spin 放在整個頁面的中間,所需要的 size 很可能會不同。Antd 這邊只提供可選的選項,分別是 small, default, large

indicator

隨著網站的不同,為了配合設計師的設計,我們也需要能夠隨心所欲的更改 Spin 的 icon。當然一個網站應該不太會設計成每一頁的 Spin 都長不一樣,不過 Antd 是為了讓廣大的開發者能夠使用,因此會有這個需求,如果是自己的網站要使用,應該一到兩種應該就很夠用了。

介面設計

屬性 說明 類型 默認值
className 客製化樣式 string
isLoading 是否載入中 boolean false
indicator 自定義載入符號 ReactNode, string <CircularProgress />
children 內容 ReactNode, string

元件實作

Spin 最簡單的原理就是根據 是否加載中 這個 boolean 來判斷是否要顯示載入符號,其他的部分就是根據不同情境來調整樣式。

那今天我們要實作的情境有兩種,一種是 Spin 直接當作加載元件,一種是 Spin 會成為一個加載容器,所以我的想法如下:

const Spin = ({
  indicator, isLoading, children, ...props
}) => {

  if (!children) {
    return indicator;
  }
  return (
    <SpinContainer {...props}>
      {children}
      {isLoading && indicator}
    </SpinContainer>
  );
};

主要核心的想法到目前為止就已經差不多了,其他的部分就是樣式上的調整。

當我們要直接 show 出一個 Spin:

<Spin />

我預設的樣式就會是這樣:

Custom Indicator

當然這個樣式我偷懶是直接拿 MUI 的 <CircularProgress /> 來使用。

如果我們要客製化樣式也是可以的,假設我們今天拿到一個 SVG 檔,我把它轉換成 JS:

export const SpinnerIcon = (props) => (
  <svg
    aria-hidden="true"
    focusable="false"
    data-prefix="fas"
    data-icon="spinner"
    role="img"
    xmlns="http://www.w3.org/2000/svg"
    viewBox="0 0 512 512"
    className="svg-inline--fa fa-spinner fa-w-16 fa-3x"
    {...props}
  >
    <path fill="currentColor" d="M304 48c0 ...." className="" />
  </svg>
);

然後我就能夠自己做一個 indicator,當作 props 傳入,或是直接做進 Spin 元件當作預設的 indicator,我這邊以 props 傳入為例:

import styled, { keyframes } from 'styled-components';

const rotateAnimation = keyframes`
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(360deg);
  }
`;

const RotateContainer = styled.div`
  width: 40px;
  height: 40px;
  animation: ${rotateAnimation} 1000ms ease-in-out infinite;
`;

<Spin
  indicator={(
    <RotateContainer>
      <FaSpinner />
    </RotateContainer>
  )}
/>

就會是這樣:

Spin As Container

再來就是我們把 Spin 當作一個容器,讓他的 children 有載入狀態,使用起來如下:

<Spin isLoading>
  <Content />
</Spin>

我的作法提供給大家參考:

<SpinContainer {...props}>
  {children}
  {isLoading && (
    <>
      <Mask />
      <Indicator
        ref={indicatorRef}
        className="spin__indicator"
        $indicatorSize={indicatorSize}
      >
        {indicator}
      </Indicator>
    </>
  )}
</SpinContainer>

主要的想法就是 <SpinContainer /> 這一層設為 position: relative;,然後裡面的 <Indicator /> 設為 position: absolute; 讓他能夠蓋在內容上面,並做置中的定位。

然後可以看到我這邊多給一個 <Mask /> 元件,主要是如果直接 show 出 indicator 疊在內容上的話,會顯得有點繚亂,所以我想要弱化載入中時內容的顯眼度,來凸顯加載中的樣式,以下就是今天的成果展示:


Spin 元件原始碼:
Source code

Storybook:
Spin


上一篇
【Day23】導航元件 - Pagination
下一篇
【Day25】反饋元件 - Skeleton
系列文
30 天擁有一套自己手刻的 React UI 元件庫30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言