基于canvas绘制边框环绕进度条

基于canvas绘制边框环绕进度条

案例

如下图所示,我们想通过canvas来绘制一个环形的进度条,且封装成组件,让进度条能自适应宽高包裹元素,进度条的进度依据百分比进行渲染
在这里插入图片描述

思路

  1. 父盒子设置相对定位
  2. canvas画布基于父盒子绝对定位,设置其宽高百分比为父盒子100%
  3. canvas画布首先绘制一个矩形边框,让其渲染出进度条底色
  4. 设置定时器,依据时间流速,先后绘制上边框进度条、右边框进度条、下边框进度条、左边框进度条,每个进度条所耗时间占总时间的1/4
  5. 进度条走完之后,清除定时器
  6. 进度条封装成组件,以插槽形式包裹需要包裹的元素

完整代码

进度条组件BorderProgress

import React, { Component, createRef } from 'react';
import './BorderProgress.less';



type StateType = {
  // borderProgressDomWidth: number;
  // borderProgressDomHeight: number;
  percent: string;
  [propName: string]: any;
};

type PropType = {
  time: number;
  pauseProgress?: boolean;
  isShowEndProgress?: boolean;
  canvasId: string;
  [propName: string]: any;
};

interface BorderProgress {
  state: StateType;
  props: PropType;
  borderProgressDom: any;
  progressInterval: any;
}

class BorderProgress extends Component {
  constructor(props: any) {
    super(props);
    this.state = {
      // borderProgressDomWidth: 0,
      // borderProgressDomHeight: 0,
      percent: '0%',
    };
    this.borderProgressDom = createRef();
    this.progressInterval = null;
  }

  componentDidMount() {
    // this.setState({
    //   borderProgressDomWidth: this.borderProgressDom.current.offsetWidth,
    //   borderProgressDomHeight: this.borderProgressDom.current.offsetHeight,
    // })
    if (!this.props.isShowEndProgress) {
      this.initBorderProgress();
    }
  }

  componentWillUnmount() {
    clearInterval(this.progressInterval);
  }

  componentDidUpdate() {
    if (this.props.pauseProgress === true) {
      clearInterval(this.progressInterval);
    }
  }

  private initBorderProgress = (): void => {
    let myCanvas: any = document.getElementById(this.props.canvasId);
    let ctx = myCanvas.getContext("2d");
    const myCanvasWidth = myCanvas.width; // canvas画布中能准确设置像素的宽度(依据dom真实宽度设置画布像素并不准确,不能有效占满画布)
    const myCanvasHeight = myCanvas.height; // canvas画布中能准确设置像素的高度(依据dom真实宽度设置画布像素并不准确,不能有效占满画布)
    console.log(myCanvasWidth, myCanvasHeight);
    const borderWidth = 10;
    // 先绘制画布原始边框矩形
    ctx.lineWidth=borderWidth; // 设置边框的线条宽度为8个像素
    ctx.strokeStyle="#333"; // 设置线条颜色
    ctx.rect(5, 5, myCanvasWidth-borderWidth, myCanvasHeight-borderWidth); // 设置边框所在坐标,是因为线条宽度也占据了像素,所以坐标x,y设置为线条宽度的一半,且矩形宽高需要减去线条宽度,这样才能让矩形沿着画布边缘,铺满画布
    ctx.stroke();
    this.startProgress(ctx, myCanvasWidth-borderWidth, myCanvasHeight-borderWidth, borderWidth);
  }

  private startProgress = (ctx: any, canSetWidth: number, canSetHeight: number, borderWidth: number): void => {
    clearInterval(this.progressInterval);
    let time = this.props.time;
    let timeCount = 0;
    let percent = `0%`;
    let topProgressBorderLength = 0;
    let rightProgressBorderLength = 0;
    let bottomProgressBorderLength = 0;
    let leftProgressBorderLength = 0;
    this.progressInterval = setInterval(() => {
      if (topProgressBorderLength < canSetWidth) { // 如果上边框小于当前canvas画布可设置宽度
        // 绘制矩形进度条top边框
        ctx.beginPath(); // 起始一条路径
        ctx.lineWidth = borderWidth; // 设置边框的线条宽度为8个像素
        ctx.strokeStyle = "#5A4CDB"; // 设置线条颜色
        ctx.lineCap = "round"; // 向线条的每个末端添加圆形线帽
        topProgressBorderLength += canSetWidth / (time * 1000 / 4 / 15);
        if (topProgressBorderLength >= canSetWidth) {
          topProgressBorderLength = canSetWidth;
        }
        ctx.moveTo(0, borderWidth/2);
        ctx.lineTo(topProgressBorderLength, borderWidth/2);
        ctx.stroke();
      }
      if (!(topProgressBorderLength < canSetWidth) && (rightProgressBorderLength < canSetHeight)) { // 如果上边框已经绘制完成,右边框小于当前canvas画布可设置高度
        // 绘制矩形进度条right边框
        ctx.beginPath(); // 起始一条路径
        ctx.lineWidth = borderWidth; // 设置边框的线条宽度为8个像素
        ctx.strokeStyle = "#5A4CDB"; // 设置线条颜色
        ctx.lineCap = "round"; // 向线条的每个末端添加圆形线帽
        rightProgressBorderLength += canSetHeight / (time * 1000 / 4 / 15);
        if (rightProgressBorderLength >= canSetHeight) {
          rightProgressBorderLength = canSetHeight;
        }
        ctx.moveTo(canSetWidth+(borderWidth/2), (borderWidth/2));
        ctx.lineTo(canSetWidth+(borderWidth/2), rightProgressBorderLength);
        ctx.stroke();
      }
      if (!(rightProgressBorderLength < canSetHeight) && (bottomProgressBorderLength < canSetWidth)) { // 如果右边框已经绘制完成,下边框小于当前canvas画布可设置宽度
        // 绘制矩形进度条bottom边框
        ctx.beginPath(); // 起始一条路径
        ctx.lineWidth = borderWidth; // 设置边框的线条宽度为8个像素
        ctx.strokeStyle = "#5A4CDB"; // 设置线条颜色
        ctx.lineCap = "round"; // 向线条的每个末端添加圆形线帽
        bottomProgressBorderLength += canSetWidth / (time * 1000 / 4 / 15);
        if (bottomProgressBorderLength >= canSetWidth) {
          bottomProgressBorderLength = canSetWidth;
        }
        ctx.moveTo(canSetWidth+(borderWidth/2), canSetHeight+(borderWidth/2));
        ctx.lineTo(canSetWidth+(borderWidth/2)-bottomProgressBorderLength, canSetHeight+(borderWidth/2));
        ctx.stroke();
      }
      if (!(bottomProgressBorderLength < canSetWidth) && (leftProgressBorderLength < canSetHeight)) { // 如果上边框已经绘制完成,右边框小于当前canvas画布可设置高度
        // 绘制矩形进度条right边框
        ctx.beginPath(); // 起始一条路径
        ctx.lineWidth = borderWidth; // 设置边框的线条宽度为8个像素
        ctx.strokeStyle = "#5A4CDB"; // 设置线条颜色
        ctx.lineCap = "round"; // 向线条的每个末端添加圆形线帽
        leftProgressBorderLength += canSetHeight / (time * 1000 / 4 / 15);
        if (leftProgressBorderLength >= canSetHeight) {
          leftProgressBorderLength = canSetHeight;
        }
        ctx.moveTo((borderWidth/2), canSetHeight+(borderWidth/2));
        ctx.lineTo((borderWidth/2), canSetHeight-leftProgressBorderLength);
        ctx.stroke();
      }

      // 绘制百分比进度文字数值
      timeCount += 15;
      percent = `${Math.floor(timeCount/(time * 1000) * 100)}%`;
      if (Math.floor(timeCount/(time * 1000) * 100) > 99) {
        percent = `99%`;
      }
      this.setState({
        percent,
      });
      // ctx.font="30px Arial";    
      // ctx.textAlign="center";     
      // ctx.fillText(`${percent}`,150,120);
      if (leftProgressBorderLength === canSetHeight) {
        clearInterval(this.progressInterval);
      }
    }, 15);
  }

  render() {
    const { percent } = this.state;
    const { canvasId } = this.props;

    return (
      <div className="border-progress" ref={this.borderProgressDom}>
        {this.props.children}
        <canvas id={canvasId} className="my-canvas"></canvas>
        <div className="border-progress-percent">{percent}</div>
      </div>
    )
  }
}

export default BorderProgress;

进度条组件BorderProgress样式:

.border-progress {
  display: inline-block;
  position: relative;

  .my-canvas {
    width: 100%;
    height: 100%;
    position: absolute;
    opacity: 1;
    top: 0;
    left: 0;
  }

  .border-progress-percent {
    width: 100%;
    height: 100%;
    position: absolute;
    display: flex;
    justify-content: center;
    align-items: center;
    font-size: 30px;
    font-weight: 700;
    color: #fff;
    top: 0;
    left: 0;
  }
}

使用进度条组件BorderProgress:

<div className="draft-list-item draft-list-item-nodata" key={index}>
  <BorderProgress time={60} key={index} canvasId={ele.videoInfoNo}>
    <div className="border-progress-content"></div>
  </BorderProgress>
</div>
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值