基于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>
目录
相关文章
|
2月前
|
前端开发 JavaScript
鼠标移动淡入淡出Canvas小球效果_特炫
本文通过HTML和JavaScript代码示例展示了如何实现鼠标移动时在Canvas上生成彩色小球并具有淡入淡出效果的动画,涉及Canvas的尺寸调整、小球对象的创建、颜色随机化、透明度变化和动画循环渲染等技术点。
35 1
鼠标移动淡入淡出Canvas小球效果_特炫
|
4月前
|
前端开发
Canvas如何画一个线条,画布效果最好添加字体和线条回溯
Canvas如何画一个线条,画布效果最好添加字体和线条回溯
|
4月前
|
移动开发 前端开发 HTML5
Canvas画布之100个小球弹射源码
Canvas画布之100个小球弹射源码
|
6月前
|
前端开发
canvas详解04-绘制文字
canvas详解04-绘制文字
78 2
canvas详解04-绘制文字
|
6月前
|
Web App开发 前端开发
canvas详解02-样式和颜色控制
canvas详解02-样式和颜色控制
114 1
|
移动开发 前端开发 JavaScript
使用Canvas绘制图形和动画
使用Canvas绘制图形和动画
199 0
|
XML 前端开发 Java
用Canvas绘制一个数字键盘
用Canvas轻轻松松搞一个数字键盘,本身没什么难度,这种效果实现的方式也是多种多样,这篇只是其中的一种,要说本篇有什么特别之处,可能就是纯绘制,没有用到其它的任何资源,一个类就搞定了。
114 0
|
前端开发 算法 JavaScript
使用 canvas 拖拽绘制矩形(带撤销)
之前预览网络摄像头的需求又有了下文,要在视频预览之上进行拖拽生成矩形边框,用于后台算法对区域内容进行一些处理。
335 0
|
前端开发
Canvas绘制圆形头像
Canvas绘制圆形头像
243 0
Canvas绘制圆形头像
|
前端开发 JavaScript
canvas-渐变文字
html要求: body这里的onload一定要写,在这个处理模式下,是在body这里执行加载页面完成后加载canvas的命令。有的写在了js中的window.onload=function(){},这里就要换一种写法了。
869 0