前言
早些时候写过的一个大屏项目,其中的环形进度条是使用css来实现的,但是在不同分辨率的情况下会有点问题,如下图所示:**
为避免这样的问题,现在尝试对以前的功能进行修改,使用canvas进行绘制,并能自定义颜色和文字样式。
1.获取目标div大小,并创建画布,定义需要的值:
<div id="myCanvas" style="width:100px;height:100px"></div>
<script>
// 获取 canvas 元素和 2D 绘图上下文对象
const container = document.getElementById(arg.container);
if (!container) {
// 容器不存在
throw new Error('容器不存在')
}
const canvas = document.createElement('canvas')
// 设置画布在页面上的大小
canvas.style = `width:${container.clientWidth}px; height:${container.clientHeight}px;`;
const ctx = canvas.getContext('2d');
// 设置画布内容是容器的两倍不容易出现图像边缘不清晰
canvas.width = container.clientWidth * 2
canvas.height = container.clientHeight * 2
container.append(canvas)
// 设置进度条背景颜色和前景颜色
// 进度条宽度
const progressSize = arg.progressSize || 23
// 圆大小
const circleSize = canvas.width / 2
// 中间圆大小
const circle_center = circleSize - progressSize
// 圆xy坐标
const cx = canvas.width / 2
const cy = canvas.height / 2
// 剪切同心圆需要的
let step = 0.8
// 圆的初始角度和结束时的角度
let inputData = typeof +arg.data === 'number' && !isNaN(+arg.data) ? arg.data : 0
// 进度条从0-100的角度是从0.7-2.3
const initialProgress = 0.7
const totalFrames = 2.3;
let progressValue = initialProgress
// 根据传进来的数值,计算目前的角度
let angle = (totalFrames - initialProgress) / 100
let progressEnd = initialProgress + angle * inputData;
2.绘制圆,因为我们所需要的圆是缺了一角的,使用arc() 方法进行画圆,如下图所示
根据官方的api,设置开始角度为0.7,结束角度为2.3,绘制进度条背景圆
// 绘制进度条背景
ctx.beginPath();
ctx.moveTo(cx, cy)
ctx.fillStyle = arg.backgroundColor;
ctx.arc(cx, cy, circleSize, initialProgress * Math.PI, totalFrames * Math.PI)
ctx.fill()
效果如下所示:
3.使用同样的方法绘画前景圆,并添加动画效果:
// 定义动画函数,每帧更新进度值并重新绘制进度条
function animate () {
if (progressValue >= progressEnd) {
return
}
// 更新进度值
progressValue += 0.03;
if (progressValue > progressEnd) {
progress = progressEnd
}
// 渐变颜色
ctx.beginPath();
let grd;
if (arg.LinearGradient && Object.keys(arg.LinearGradient).length != 0) {
grd = ctx.createLinearGradient(0, 0, canvas.width, canvas.height)
grd.addColorStop(0, arg.LinearGradient.start || arg.LinearGradient.stop)
grd.addColorStop(1, arg.LinearGradient.stop || arg.LinearGradient.start)
} else {
grd = arg.foregroundColor || '#1B64ED'
}
ctx.fillStyle = grd
ctx.moveTo(cx, cy)
// 2.3满格
ctx.arc(cx, cy, circleSize, initialProgress * Math.PI, progressValue * Math.PI);
ctx.fill()
// 检查是否达到最大帧数,如果没有则继续动画,否则停止动画
requestAnimationFrame(animate);
step = 0
// 剪切圆
clearArc(cx, cy, circle_center)
}
4.中心剪切圆,因为没有直接剪切掉圆形的方法,所以使用很多个矩形堆叠成圆形的方法进行剪切,剪切完成之后添加中间的文字。
function clearArc (x, y, radius) {
let arcWidth = radius - step
// Math.sqrt 求平方根
let arcHeight = Math.sqrt(radius * radius - arcWidth * arcWidth)
let posX = x - arcWidth
let posY = y - arcHeight
let widthX = 2 * arcWidth
let heightY = 2 * arcHeight
if (step <= radius) {
ctx.clearRect(posX, posY, widthX, heightY)
step += 0.8
clearArc(x, y, radius)
} else {
// 文字
if (arg.fontShow) {
ctx.font = `${arg.fontSize || 16}px ${arg.fontFamily}`;
ctx.fillStyle = arg.fontColor || '#000'
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'
ctx.fillText(inputData + '%', cx, cy);
}
}
}
完整的代码如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>环形进度条</title>
</head>
<style>
@font-face {
font-family: ledFont;
src: url('./font/DS-DIGI.TTF'); /*自定义字体*/
}
</style>
<body>
<div style="display: flex">
<div id="myCanvas" style="width:100px;height:100px"></div>
</div>
</body>
<script>
initProgress({
container: 'myCanvas', // 容器
backgroundColor: '#ddd', // 进度条背景颜色
foregroundColor: '#000', // 进度条前景色
LinearGradient: { // 如果前景色需要设置渐变
start: '#1B64ED',
stop: '#04E8F6'
},
progressSize: 22, // 进度条宽度
data: 83.33,
fontShow: true,
fontSize: 40,
fontFamily: 'ledFont',
fontColor: '#16B7C8'
})
function initProgress (arg) {
// 获取 canvas 元素和 2D 绘图上下文对象
const container = document.getElementById(arg.container);
if (!container) {
// 容器不存在
throw new Error('容器不存在')
}
const canvas = document.createElement('canvas')
canvas.style = `width:${container.clientWidth}px; height:${container.clientHeight}px;`;
const ctx = canvas.getContext('2d');
// 画布是容器的两倍不容易出现图像边缘不清晰
canvas.width = container.clientWidth * 2
canvas.height = container.clientHeight * 2
container.append(canvas)
// 设置进度条背景颜色和前景颜色
// 进度条宽度
const progressSize = arg.progressSize || 23
// 圆大小
const circleSize = canvas.width / 2
// 中间圆大小
const circle_center = circleSize - progressSize
// 圆xy坐标
const cx = canvas.width / 2
const cy = canvas.height / 2
// 剪切同心圆需要的
let step = 0.8
// 圆的初始角度和结束时的角度
let inputData = typeof +arg.data === 'number' && !isNaN(+arg.data) ? arg.data : 0
// 进度条从0-100的角度是从0.7-2.3
const initialProgress = 0.7
const totalFrames = 2.3;
let progressValue = initialProgress
// 根据传进来的数值,计算目前的角度
let angle = (totalFrames - initialProgress) / 100
let progressEnd = initialProgress + angle * inputData;
// 绘制进度条背景
ctx.beginPath();
ctx.moveTo(cx, cy)
ctx.fillStyle = arg.backgroundColor;
ctx.arc(cx, cy, circleSize, initialProgress * Math.PI, totalFrames * Math.PI)
ctx.fill()
// 切掉圆心 圆心(x,y),半径radius
function clearArc (x, y, radius) {
let arcWidth = radius - step
// Math.sqrt 求平方根
let arcHeight = Math.sqrt(radius * radius - arcWidth * arcWidth)
let posX = x - arcWidth
let posY = y - arcHeight
let widthX = 2 * arcWidth
let heightY = 2 * arcHeight
if (step <= radius) {
ctx.clearRect(posX, posY, widthX, heightY)
step += 0.8
clearArc(x, y, radius)
} else {
// 文字
if (arg.fontShow) {
ctx.font = `${arg.fontSize || 16}px ${arg.fontFamily}`;
ctx.fillStyle = arg.fontColor || '#000'
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'
ctx.fillText(inputData + '%', cx, cy);
}
}
}
// 定义动画函数,每帧更新进度值并重新绘制进度条
function animate () {
if (progressValue >= progressEnd) {
return
}
// 更新进度值
progressValue += 0.03;
if (progressValue > progressEnd) {
progressValue= progressEnd
}
// 渐变颜色
ctx.beginPath();
let grd;
if (arg.LinearGradient && Object.keys(arg.LinearGradient).length != 0) {
grd = ctx.createLinearGradient(0, 0, canvas.width, canvas.height)
grd.addColorStop(0, arg.LinearGradient.start || arg.LinearGradient.stop)
grd.addColorStop(1, arg.LinearGradient.stop || arg.LinearGradient.start)
} else {
grd = arg.foregroundColor || '#1B64ED'
}
ctx.fillStyle = grd
ctx.moveTo(cx, cy)
// 2.3满格
ctx.arc(cx, cy, circleSize, initialProgress * Math.PI, progressValue * Math.PI);
ctx.fill()
// 检查是否达到最大帧数,如果没有则继续动画,否则停止动画
requestAnimationFrame(animate);
step = 0
clearArc(cx, cy, circle_center)
}
// 开始动画
animate();
}
</script>
</html>