iT邦幫忙

2021 iThome 鐵人賽

DAY 26
0
Modern Web

30 天擁有一套自己手刻的 React UI 元件庫系列 第 26

【Day26】反饋元件 - Progress bar

元件介紹

Progress bar 是能夠展示當前進度的進度條元件。當一個操作需要顯示目前百分比,或是需要較長時間等待運行的時候,能夠使用這樣的元件提示用戶目前進度,藉此來緩解用戶等待的焦慮感,或者提供使用者完成複雜任務的成就感。

以下舉例可能會使用到進度條的情境:

  • 上傳、下載檔案的進度
  • 線上課程網站的課程完成進度
  • 募資網站的募資進度
  • 填寫複雜表單的時候顯示已完成、剩餘完成的進度

參考設計 & 屬性分析

其實 Progress bar 某種程度上我覺得跟先前提到的 Slider 元件在結構上有點像,會需要一個軌道 rail,在軌道上面有目前的進度 track 條,再厲害一點就是可以在進度條的旁邊加上進度數字。

下圖是 Antd 的 Progress bar 結構,看起來結構上也是符合我們的直覺,也蠻單純的,ant-progress-inner 就是 rail 元件,ant-progress-bg 就是 track 元件,彼此的關係是 parent and children;另外我們也可以看到 ant-progress-text 是我們的進度百分比,然後主要的 bar 結構跟文字結構是同一層,外面再用一層 div 包起來來幫助他們做排版。

再來我們來看 MUI 的 Progress bar,看起來結構上應該是跟 Antd Progress bar 一模一樣

接下來我們來看一些大家都會設計的 props

進度

Antd 的進度數值 props 叫做 percent,而 MUI 則叫做 value,其實我個人覺得 value 會有點看不出來到底範圍值在哪裡,只是文件上面有說明是 0~100。

在 MUI Progress bar 的 value 裏面我們故意塞了一個超出 100 的數字,他沒有禁止你這麼做,但是果然是跑出一個非預期的樣子。

在 Antd Progress bar 裡面我們一樣塞了 120 這個數字,我們可以看到在 ant-progress-bg 的地方是 width: 100%,所以如果超出 100 的話,應該就是自動幫你修正成 100。這樣的設計我覺得會比較符合我對這個元件的預期,只不過我沒有很喜歡他自作主張的幫我變色,還幫我把數字變成綠色勾勾。

顏色

MUI 一樣是統一他的風格,props 用 color,內容一樣是只支援 primary, secondary 保留字,要客製化的話需要透過他的 JSS。

Antd 就直接分成進度條顏色(strokeColor)以及未完成的分段顏色(trailColor),以套件使用者的叫度來看,我看到這兩個 props 命名其實是有點怪怪的。

但我的猜想是這樣的,Antd Progress bar 支援 Linear Progress 以及 Circular Progress,Linear Progress 大部分是由 Html div 結構所組成,而 Circular Progress 其實就是一個 SVG,而 strokeColor 應該是取自 SVG 的參數命名。但是 Linear Progress 明明就不是 SVG 結構,但卻用了 SVG 的參數命名,我個人是覺得有點不太搭。

但以目前為止我們設計的元件,為了一致性,track 的顏色應該會統一叫做 themeColor,傳入顏色一樣支援 primary, secondary 關鍵字以及色票,另外 railColor 其實比較少會有需要去改變它,所以該脆不設計 props 我覺得應該也沒關係,但若要設計的話,會希望與 rail 元件的名稱一致,所以應該會叫做 railColor。

是否顯示進度數值或狀態圖標

如果在專案上面很常用需要顯示進度數值的話,我覺得加入像 Antd 這個 showInfo 的 props 設計也是很方便

但也有可能有些專案不是很需要顯示這個數值,或者說數值是顯示在 bar 上面,如下圖所示,那我覺得也是按照自己的需要做調整就可

介面設計

屬性 說明 類型 默認值
className 客製化樣式 string
value 進度 number 0
themeColor 主題配色,primary、secondary 或是自己傳入色票 primary, secondary, 色票 primary
showInfo 是否顯示進度數值 boolean true
isStatusActive 是否顯示等待進度動畫 boolean false

元件實作

我們希望用下面這樣一個簡單的形式就能夠得到一個進度條:

<ProgressBar value={50} />

其實我們進度條跟 Slider 元件有點類似,不妨可以一起參考。

因為一個進度條主要會需要 trackrail 兩個元素,所以以下是主要的結構:

<Trail className="progress-bar__trail">
  <Track
    className="progress-bar__track"
    $color={color}
    $value={value}
    $isStatusActive={isStatusActive}
  />
</Trail>

那我們這次也加入了如同進度數值的資料,所以在同一層加入 Info ,並且透過父層做排版,如下:

const StyledProgressBar = styled.div`
  display: flex;
  align-items: center;
`;


<StyledProgressBar className={className}>
  <Trail className="progress-bar__trail">
    <Track
      className="progress-bar__track"
      $color={color}
      $value={value}
      $isStatusActive={isStatusActive}
    />
  </Trail>
  {showInfo && (
    <Info className="progress-bar__info">
      {`${value}%`}
    </Info>
  )}
</StyledProgressBar>

那我們的進度條元件的主幹就完成了!

數值限制

我們希望我們的數值可以顯示超過 100%,然後最小不能低於 0%。
不過雖然數值能夠超過 100%,但是 track 的長度總不能無限超出 trail 長度,否則會破版,因此我們還是將 track 的長度限制在 0~100 之間,所以在 track 的 value 我們需要做一些限制:

const formatValue = (value) => {
  if (value > 100) {
    return 100;
  }
  if (value < 0) {
    return 0;
  }
  return value;
};


<Track
  {...略}
  $value={formatValue(value)}
/>

效果如下圖,如果數值超過 100%,那我們的 bar 還是顯示 100%。

客製化顏色

跟之前的招數如初一徹,我們用準備好的 useColor 來處理我們的顏色:

const { makeColor } = useColor();
const color = makeColor({ themeColor });

跟之前的元件一樣,我們允許讓他隨意可以變色:

漸層 track 顏色

我們其實也常看見一些進度條會有漸層的變化,例如從左到右越來越深色,表示進度即將要完成,用前面說的 themeColor 做不到怎麼辦?

沒關係,我們在 <Track /> 上面有留下 className,因此可以幫助我們隨意調整樣式:

<Track
  className="progress-bar__track"
  {...略}
/>

所以我們就能夠這樣做:

import ProgressBar from '../components/ProgressBar';

const GradientProgressBar = styled(ProgressBar)`
  .progress-bar__track {
    background: linear-gradient(45deg, #FF8E53 30%, #FE6B8B 90%);
  }
`;

<GradientProgressBar {...args} />

達到的效果如下,讓你炫砲得不要不要的:

isStatusActive,光暈移動動畫

等待進度的時間總是特別漫長,所以總是需要做一些奇怪的動畫讓用戶覺得你的系統真的有在忙著幫他處理事情,那我們何不把上一篇 Skeleton 的光暈移動動畫抄過來用呢?

所以我們可以在 <Track /> 加上一個 Boolean props,這邊我絞盡我殘破的英文來取一個叫做 isStatusActive 的參數,藉此來控制要不要顯示這個動畫:

<Track
  {...略}
  $isStatusActive={isStatusActive}
/>

動畫方面我的範例如下:

const activeAnimation = css`
  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 Track = styled.div`
  /* ...略 */
  ${(props) => props.$isStatusActive && activeAnimation}
`;

原理跟前篇一樣,我用一個為元素當作漸層光暈,在背景上面重複由左往右移動,超出的部分就用 overflow: hidden; 處理掉,詳細原理的部分可參考前篇,我做出來的效果如下:

看起來就覺得這個系統真的很忙,有在認真!

進度條慢慢長出來

那如果我們想要讓人家看到我們進度條努力長出來的過程,可以加上一些 setInterval 的動畫,那我這個範例是沒有將這個功能做進去 <ProgressBar /> 元件裡面,我怕裡面做的事情太複雜,所以我把他拉到外面做,但如果有考量到很常用到這個效果,或許再把他做進去元件裡面也不遲,或是另外再基於 <ProgressBar /> 做出另一個元件也可以:

const [playKey, setPlayKey] = useState(true);
const [transitionValue, setTransitionValue] = useState(0);

useEffect(() => {
  let intervalId;
  setTransitionValue(0);
  if (playKey) {
    setPlayKey(false);
    intervalId = setInterval(() => {
      setTransitionValue((prev) => {
        if (prev >= 120) {
          clearInterval(intervalId);
        }
        return prev + 1;
      });
    }, 30);
  }
}, [playKey]);

Demo 的地方我做一個重播按鈕,按下去改變 playKey ,藉此觸發 useEffect 內容可以再次執行:

<Button onClick={() => setPlayKey(true)}>
  重播
</Button>

然後藉由上面 setInterval,我們不斷的改變 progress 的 value,慢慢把他 +1 加上去,直到終點進度為止就停止加總:

<ProgressBar {...args} value={transitionValue < 50 ? transitionValue : 50} />

最後一定要記得在 <Track /> 上面的 width 加上 transition,這樣 width 在變長的時候,才會有連續的過場動畫,不會一格跳一格的跳上去:

const Track = styled.div`
  background: ${(props) => props.$color};
  width: ${(props) => props.$value}%;
  height: 8px;
  border-radius: 50px;
  transition: width 0.2s;
  ${(props) => props.$isStatusActive && activeAnimation}
`;

這樣就可以看到下面這樣慢慢長出來的效果啦!


ProgressBar 元件原始碼:
Source code

Storybook:
ProgressBar


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

尚未有邦友留言

立即登入留言