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

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

案例

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

image.png

思路

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

完整代码

进度条组件BorderProgress

importReact, { Component, createRef } from'react';
import'./BorderProgress.less';
typeStateType= {
// borderProgressDomWidth: number;// borderProgressDomHeight: number;percent: string;
  [propName: string]: any;
};
typePropType= {
time: number;
pauseProgress?: boolean;
isShowEndProgress?: boolean;
canvasId: string;
  [propName: string]: any;
};
interfaceBorderProgress {
state: StateType;
props: PropType;
borderProgressDom: any;
progressInterval: any;
}
classBorderProgressextendsComponent {
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);
    }
  }
privateinitBorderProgress= (): void=> {
letmyCanvas: any=document.getElementById(this.props.canvasId);
letctx=myCanvas.getContext("2d");
constmyCanvasWidth=myCanvas.width; // canvas画布中能准确设置像素的宽度(依据dom真实宽度设置画布像素并不准确,不能有效占满画布)constmyCanvasHeight=myCanvas.height; // canvas画布中能准确设置像素的高度(依据dom真实宽度设置画布像素并不准确,不能有效占满画布)console.log(myCanvasWidth, myCanvasHeight);
constborderWidth=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);
  }
privatestartProgress= (ctx: any, canSetWidth: number, canSetHeight: number, borderWidth: number): void=> {
clearInterval(this.progressInterval);
lettime=this.props.time;
lettimeCount=0;
letpercent=`0%`;
lettopProgressBorderLength=0;
letrightProgressBorderLength=0;
letbottomProgressBorderLength=0;
letleftProgressBorderLength=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 (
<divclassName="border-progress"ref={this.borderProgressDom}>        {this.props.children}
<canvasid={canvasId} className="my-canvas"></canvas><divclassName="border-progress-percent">{percent}</div></div>    )
  }
}
exportdefaultBorderProgress;

进度条组件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:

<divclassName="draft-list-item draft-list-item-nodata"key={index}><BorderProgresstime={60} key={index} canvasId={ele.videoInfoNo}><divclassName="border-progress-content"></div></BorderProgress></div>
目录
相关文章
|
1月前
|
人工智能 前端开发 流计算
前端的同学,终于要起飞啦,Github 6.3k star + ,免费可商用的UI元素库!!!
小华同学推荐:galaxy 是一个免费可商用的开源 UI 元素库,收录超 3,000 个组件,支持 CSS 与 Tailwind 双格式,兼容 Figma/React/HTML,助力高效开发与设计协作。
406 7
webpack优化篇(四十):速度分析:使用 speed-measure-webpack-plugin
webpack优化篇(四十):速度分析:使用 speed-measure-webpack-plugin
2231 0
webpack优化篇(四十):速度分析:使用 speed-measure-webpack-plugin
|
机器学习/深度学习 前端开发 JavaScript
WebAssembly:让前端性能突破极限的秘密武器
WebAssembly(简称 WASM)作为前端开发的性能加速器,能够让代码像 C++ 一样在浏览器中高速运行,突破了 JavaScript 的性能瓶颈。本文详细介绍了 WebAssembly 的概念、工作原理以及其在前端性能提升中的关键作用。通过与 JavaScript 的配合,WASM 让复杂运算如图像处理、3D 渲染、机器学习等在浏览器中流畅运行。文章还探讨了如何逐步集成 WASM,展示其在网页游戏、高计算任务中的实际应用。WebAssembly 为前端开发者提供了新的可能性,是提升网页性能、优化用户体验的关键工具。
5994 2
WebAssembly:让前端性能突破极限的秘密武器
|
JavaScript
element-UI el-table动态显示隐藏列造成固定一侧的列(fixed=“left/right“)错误显示
问题原因:多个tabs共用一个实体,动态显示隐藏列 出现了固定在右侧的列(fixed="right")错位 【解决方案】 表格的重新布局,只要table数据发生变化的时候就重新渲染表格 ```js this.$nextTick(() => { this.$refs.formname.doLayout() }) ``` 参考element官方文档 ![请在此添加图片描述](https://developer-private-1258344699.cos.ap-guangzhou.myqcloud.com/column/article/5877188/20231030-e40
743 0
element-UI el-table动态显示隐藏列造成固定一侧的列(fixed=“left/right“)错误显示
|
JavaScript API 调度
Pinia进阶:优雅的setup(函数式)写法+封装
相信在座各位假如使用Vue生态开发项目情况下,对Pinia状态管理库应该有所听闻或正在使用,假如还没接触到Pinia,这篇文章可以帮你快速入门,并如何在企业项目中更优雅封装使用。
575 154
|
移动开发 算法 前端开发
前端常用算法全解:特征梳理、复杂度比较、分类解读与示例展示
前端常用算法全解:特征梳理、复杂度比较、分类解读与示例展示
370 0
|
前端开发 UED
触屏新体验:CSS动画让移动端底部导航活起来!
触屏新体验:CSS动画让移动端底部导航活起来!
|
机器学习/深度学习 算法 数据挖掘
基于GWO灰狼优化的CNN-LSTM的时间序列回归预测matlab仿真
本项目展示了一种结合灰狼优化(GWO)与深度学习模型(CNN和LSTM)的时间序列预测方法。GWO算法高效优化模型超参数,提升预测精度。CNN提取局部特征,LSTM处理长序列依赖,共同实现准确的未来数值预测。项目包括MATLAB 2022a环境下运行的完整代码及视频教程,代码内含详细中文注释,便于理解和操作。
|
存储 分布式计算 NoSQL
DataX深度剖析:解读数据传输工具的设计理念与架构特点
DataX深度剖析:解读数据传输工具的设计理念与架构特点
1279 5
DataX深度剖析:解读数据传输工具的设计理念与架构特点
|
SQL Java 数据库连接
Mybatis-Plus删除操作以及实现逻辑删除
Mybatis-Plus删除操作以及实现逻辑删除
Mybatis-Plus删除操作以及实现逻辑删除