[CSS]矩形進度條的兩種實現

最近開發接到一個需求,前端展現付款的驗證碼,驗證碼時效 10 分鐘,到期過時,同時在二維碼的外側有一個倒計時條,本來的實現方式是經過 JS 來控制,設置左上,左下,右上,右下四個矩形,每一個矩形只顯示一個折角的邊框,從而模擬整個外框。css

根據倒計時的時間輪詢計算比例,分別控制四個矩形的寬高,從而實現倒計時的 CountDown 效果。這樣的實現方式有幾個問題:html

  1. 使用4個元素來模擬,致使加入了不少沒必要要的數據
  2. js 輪詢操做,代碼很冗長。

本文主要介紹兩種非 js 控制的矩形倒計時條的實現方法。前端

progressbar.gif

CSS 實現

css 實現方法的原理是:react

  1. 設置四個background,使用linear-gradient 造成純色的圖片背景。
  2. 設置background的 size & position,使他們分佈在元素的四周。
  3. 設置一個動畫,均分紅 4 個階段,每一個階段將背景的位置按照順時針平移。

具體能夠看代碼markdown

.progress {
  display: flex;
  justify-content: center;
  align-items: center;
  height: var(--height);
  width: var(--width);
  border-radius: calc(var(--line) / 2);
  background: 
    linear-gradient(to right, var(--green) 99.99%, var(--blue))
    calc(-1 * var(--width)) 0rem 
    / 100% var(--line),
    linear-gradient(to bottom, var(--green) 99.99%, var(--blue))
    calc(var(--width) - var(--line)) calc(-1 * var(--height)) 
    / var(--line) 100%,
    linear-gradient(to right, var(--green) 99.99%, var(--blue)) 
    var(--width) calc(var(--height) - var(--line)) 
    / 100% var(--line),
    linear-gradient(to top, var(--green), 99.99%, var(--blue)) 
    0rem var(--height) 
    / var(--line) 100%;
  background-repeat: no-repeat;
  animation: progress var(--time) linear forwards infinite;
}

@keyframes progress {
  0% {
    background-position: 
      calc(-1 * var(--width)) 0rem,
      calc(var(--width) - var(--line)) calc(-1 * var(--height)),
      var(--width) calc(var(--height) - var(--line)), 
      0rem var(--height);
  }
  25% {
    background-position: 
      0rem 0rem,
      calc(var(--width) - var(--line)) calc(-1 * var(--height)),
      var(--width) calc(var(--height) - var(--line)), 
      0rem var(--height);
  }
  50% {
    background-position: 
      0rem 0rem, 
      calc(var(--width) - var(--line)) 0rem,
      var(--width) calc(var(--height) - var(--line)), 
      0rem var(--height);
  }
  75% {
    background-position: 
      0rem 0rem, 
      calc(var(--width) - var(--line)) 0rem,
      0rem calc(var(--height) - var(--line)), 
      0rem var(--height);
  }
  100% {
    background-position: 
      0rem 0rem, 
      calc(var(--width) - var(--line)) 0rem,
      0rem calc(var(--height) - var(--line)), 
      0rem 0rem;
  }
}

複製代碼

SVG 實現

svg的實現則是hack了stroke-dasharray利用這個屬性造出間斷線來模擬倒計時,只要這個線足夠長那麼從視覺來看就是能夠造成從全滿變成全空的效果,這裏的代碼是這樣的:svg

<div class="father">
  <svg class="progressSvg" style={{'--speed': speed, '--progress': progress}} viewBox="0 0 120 120">
    <rect width="100" height="100" x="10" y="10" rx="10" ry="10" />
  </svg>
  <span class="son">{props.svg}</span>
</div>
複製代碼

主要看rect部分,設置了一個圓角,因此矩形的起始位置設置成了x="10" y="10",而且因爲設置了矩形的尺寸,爲了能放下,因此 svg 標籤的 viewBox="0 0 120 120" 從而放下這個圓角矩形。oop

這樣以來,矩形的周長就是 400,因此設置stroke-dasharray 只要大於 400 便可,爲了保險設置成 1000長度的實線,1000長度的虛線。組件化

.progressSvg rect {
  fill: none;
  stroke: blue;
  stroke-width: 4; // 控制邊框的寬度
  /* stroke-linecap: round; */
  stroke-dasharray: 1000 1000;
  stroke-dashoffset: 0;
  animation: spin 60s infinite linear;
}

複製代碼

接着就是讓它動起來,這裏使用的是控制stroke-offset來控制,就從0(徹底是邊框)轉到 -400(旋轉了全部的邊框),由於實線的前面是虛線,只要開始設置負的 offset 那麼就會是相似於被吃掉的效果。flex

@keyframes spin {
  to {
    stroke-dashoffset: -400;
  }
}
複製代碼

這樣咱們就實現了最簡單的二維碼倒計時進度條了。在線演示 codepen.io動畫

組件化 基於 React

樣式基本不須要修改,修改一下js 文件,主要經過 css 變量來對倒計時時間,進度進行控制。

這裏根據需求:

  1. 頁面在加載的時候會給出過時時間,例如總共支付時間10分鐘的話,當進度條走了 60% 以後,進度條顏色變成紅色。
  2. 根據給出的過時時間,頁面刷新的時候,保持當前的進度。
const CountedDown = (props) => {
  const [color, setColor] = React.useState("green");
  const [speed, setSpeed] = React.useState('100s');
  const [progress] = React.useState('0.75');
  return (
    <div> <div class="flex" style={{ "--bg": color, "--time": speed }}> <div class="countdown"> <div class="progress"> <div class="inner"> {props.css} </div> </div> </div> </div> <div class="father"> <svg class="progressSvg" style={{'--speed': speed, '--progress': progress}} viewBox="0 0 120 120"> <rect width="100" height="100" x="10" y="10" rx="10" ry="10" /> </svg> <span class="son">{props.svg}</span> </div> </div>
  );
}  
複製代碼

從上面的代碼中,能夠看出咱們給 css 傳入了 --bg 控制進度條的顏色,--time控制倒計時,讀者能夠自行查看在線演示代碼。因爲css版本的拐角存在問題,主要介紹svg版本。

在 svg 版本中, 傳入了 --speed 控制速度,--progress控制進度,對應的 css :

.progressSvg rect {
  fill: none;
  stroke: blue;
  stroke-width: 4;
  /* stroke-linecap: round; */
  stroke-dasharray: 1000 1000;
  - stroke-dashoffset: 0;
  - animation: spin 60s infinite linear;
  + stroke-dashoffset: calc((1 - var(--progress)) * (-400));
  + animation: spin var(--speed) infinite linear;
}
複製代碼

--speed很好理解,主要解釋--progress,上文中,咱們知道使用動畫就是讓 stroke-offset按照逆時針旋轉到 -400, 那麼保存進度就是保存這個 offset 值,當咱們認爲如今的百分比進度是0.75的話,就須要提早 手動spin (1-0.75)*(-400)

能夠用於生產的 React 組件 能夠參考下面的代碼:

/* CountDown.module.css */
.father {
  position: relative;
}
.son {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: 12rem;
  height: 100%;
  display: flex;
  justify-content: center;
  align-items: center;
}

.progress {
  width: 100%;
  height: 100%;
}

@keyframes spin {
  to {
    stroke-dashoffset: -400;
  }
}

.progress rect {
  fill: none;
  stroke: var(--color);
  stroke-width: 4;
  /* stroke-linecap: round; */
  stroke-dasharray: 1000 1000;
  stroke-dashoffset: calc(-400 * var(--rate));
  animation: spin 600s infinite linear;
  /* animation-direction: alternate; */
}
複製代碼
import React from 'react';

import styles from './CountDown.module.css';

interface MyCSSProperties extends React.CSSProperties {
  '--color': string;
  '--rate': string;
}

const CountDown = ({ color, timer, children, }: { color: string; timer: number; children: React.ReactNode; }) => {
  const style: MyCSSProperties = {
    // Add a CSS Custom Property
    '--color': color,
    '--rate': `${1 - timer / (600 * 1000)}`,
  };

  return (
    <div className={styles.father}> <svg className={styles.progress} viewBox="0 0 120 120"> <rect style={style} width="100" height="100" x="10" y="10" rx="6" ry="6" /> </svg> <span className={styles.son}>{children}</span> </div>
  );
};

export { CountDown };

複製代碼
/* usage */
import { useCountDown } from 'ahooks';
import React, { useEffect, useState } from 'react';

const Index = ()=>{
  const [barColor, setBarColor] = useState('blue'); // red
  const [expiryTimer, setTargetDate, formattedRes] = useCountDown({
    targetDate: dataRes.expiredAt,
    onEnd,
  });
  useEffect(() => {
    if (timer !== 0 && timer < 600 * 0.35 * 1000) {
      setBarColor('red');
    }
  }, [expiryTimer]);
  return (
  <CountDown color={barColor} timer={timer}> <div className={classNames({ hidden: show, })} id="qrcode" ref={qrcodeRef} /> </CountDown>
  )
}
複製代碼
相關文章
相關標籤/搜索