圆轨滑动gif:
1.初始化配置
// 基础配置
let start = 0;
let end = (5 / 3) * Math.PI;
let colorList = ["#55e3fe", "#2f87fc", "#343eeb"];
let arcWidth = "30"; //圆轨尺寸
let arcRadius = 180; // 圆轨半径
let lineRadius = 140; //虚线半径
let startTrack = false; // 开始节点是否开始运动
let endTrack = false; // 结束节点是否开始运动
let canvas = document.querySelector("#test");
let ratioW = Math.round(canvas.width / parseInt(canvas.style.width)); // 宽度占比
let ratioH = Math.round(canvas.height / parseInt(canvas.style.height)); // 高度占比
let x0 = Math.round(canvas.width / 2); //中心x轴坐标
let y0 = Math.round(canvas.height / 2); //中心y轴坐标
let lrw = Math.round(arcWidth / 2);
var ctx = canvas.getContext("2d");
var grad = null; // 边框渐变渲染只能够进行线性渲染
let startNodeX = 0, //开始节点X轴坐标
startNodeY = 0, //开始节点Y轴坐标
endNodeX = 0, //结束节点X轴坐标
endNodeY = 0; //结束节点Y轴坐标
2.开始绘制
2.1 获取开始节点,结束节点的坐标
startNodeX = Math.round(x0 + arcRadius * Math.cos(start)); //开始节点X轴坐标
startNodeY = Math.round(y0 + arcRadius * Math.sin(start)); //开始节点Y轴坐标
endNodeX = Math.round(x0 + arcRadius * Math.cos(end)); //结束节点X轴坐标
endNodeY = Math.round(y0 + arcRadius * Math.sin(end)); //结束节点Y轴坐标
2.2 处理渐变颜色
if (start != end && startNodeX == endNodeX && startNodeY == endNodeY) {
grad = ctx.createLinearGradient(
startNodeX,
startNodeY,
x0 + (x0 - startNodeX),
y0 + (y0 - startNodeX)
);
} else {
grad = ctx.createLinearGradient(
startNodeX,
startNodeY,
endNodeX,
endNodeY
);
grad.addColorStop(0, colorList[0]);
grad.addColorStop(0.5, colorList[1]);
grad.addColorStop(1, colorList[2]);
}
3.开始绘制
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(0, 0); //坐标定位至0,0处
// 1.背景颜色
ctx.fillStyle = "#040e31";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.setLineDash([1, 0]);
// 2.底部透明圆环
ctx.beginPath();
ctx.lineWidth = arcWidth;
ctx.strokeStyle = "#ccdffb55";
ctx.arc(x0, y0, arcRadius, 0, 2 * Math.PI); //绘制一个完整的圆
ctx.stroke();
// 3.渐变圆弧
ctx.beginPath();
ctx.lineWidth = arcWidth;
ctx.strokeStyle = grad;
ctx.arc(x0, y0, arcRadius, start, end);
ctx.stroke();
// 4.灰色虚细线
ctx.beginPath();
ctx.lineWidth = "20";
ctx.setLineDash([3, 8]);
ctx.strokeStyle = grad;
ctx.arc(x0, y0, lineRadius, start, end);
ctx.stroke();
// 5.渐变虚细线
ctx.beginPath();
ctx.lineWidth = "20";
ctx.strokeStyle = "#ccdffb55";
// 设置虚线排列
ctx.arc(x0, y0, lineRadius, end, start); //绘制一个完整的圆
ctx.stroke();
// 6.画圆弧两端的图标
ctx.setLineDash([1, 0]);
// 开始节点
ctx.beginPath();
ctx.lineWidth = lrw + "";
ctx.fillStyle = colorList[0];
ctx.arc(startNodeX, startNodeY, lrw, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
// 结束节点
ctx.beginPath();
ctx.fillStyle = colorList[2];
ctx.arc(endNodeX, endNodeY, lrw, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
if (startTrack) {
ctx.setLineDash([3, 4]);
// 开始节点
ctx.beginPath();
ctx.lineWidth = lrw - 8 + "";
ctx.strokeStyle = "#ffffff";
ctx.arc(startNodeX, startNodeY, lrw - 5, 0, 2 * Math.PI);
ctx.stroke();
}
if (endTrack) {
ctx.setLineDash([3, 4]);
// 结束节点
ctx.beginPath();
ctx.lineWidth = lrw - 8 + "";
ctx.strokeStyle = "#ffffff";
ctx.arc(endNodeX, endNodeY, lrw - 5, 0, 2 * Math.PI);
ctx.stroke();
}
4.以上是静态canvas圆环,若需要沿着圆轨滑动,则需要使用鼠标监听事件
// 画布鼠标按下事件
canvas.addEventListener("mousedown", function (e) {
let points = windowToCanvas(canvas, e.clientX, e.clientY);
let startDis = distance(points.x, points.y, startNodeX, startNodeY);
let endDis = distance(points.x, points.y, endNodeX, endNodeY);
if (lrw > startDis) {
startTrack = true;
ctx.setLineDash([3, 4]);
// 开始节点
ctx.beginPath();
ctx.lineWidth = lrw - 8 + "";
ctx.strokeStyle = "#ffffff";
ctx.arc(startNodeX, startNodeY, lrw - 5, 0, 2 * Math.PI);
ctx.stroke();
}
if (lrw > endDis) {
endTrack = true;
ctx.setLineDash([3, 4]);
// 结束节点
ctx.beginPath();
ctx.lineWidth = lrw - 8 + "";
ctx.strokeStyle = "#ffffff";
ctx.arc(endNodeX, endNodeY, lrw - 5, 0, 2 * Math.PI);
ctx.stroke();
}
});
// 画布鼠标松开事件
canvas.addEventListener("mouseup", function (e) {
if (startTrack) {
ctx.setLineDash([1, 0]);
// 开始节点
ctx.beginPath();
ctx.lineWidth = lrw + "";
ctx.fillStyle = colorList[0];
ctx.arc(startNodeX, startNodeY, lrw, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
}
if (endTrack) {
ctx.setLineDash([1, 0]);
// 结束节点
ctx.beginPath();
ctx.lineWidth = lrw + "";
ctx.fillStyle = colorList[2];
ctx.arc(endNodeX, endNodeY, lrw, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
}
startTrack = false;
endTrack = false;
});
// 画布鼠标移动事件
canvas.addEventListener("mousemove", function (e) {
// 节点运动轨迹
if (startTrack || endTrack) {
let points = windowToCanvas(canvas, e.clientX, e.clientY);
let angle = vectorAngle([0, 0], [x0 - points.x, y0 - points.y]);
// 开始节点
if (startTrack) start = angle;
// 结束节点
if (endTrack) end = angle;
init();
}
});
5.辅助函数
// 两点之间的距离
function distance(x1, y1, x2, y2) {
let squareSum = Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2);
return Math.round(Math.sqrt(squareSum));
}
// 求以下两个向量之间的角度
function vectorAngle(x, y) {
let angle = Math.atan2(x[1] - y[1], x[0] - y[0]);
return angle;
}
/** 获取鼠标在canvas上的坐标**/
function windowToCanvas(canvas, x, y) {
let rect = canvas.getBoundingClientRect();
return {
x: Math.round((x - rect.left * (canvas.width / rect.width)) * ratioW),
y: Math.round(
(y - rect.top * (canvas.height / rect.height)) * ratioH
),
};
}
总体代码如下:
<!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>Document</title>
<style>
body {
padding: 0;
margin: 0;
}
</style>
</head>
<body>
<canvas
id="test"
width="800"
height="800"
style="width: 400px; height: 400px"
></canvas>
<script>
// 基础配置
let start = 0;
let end = (5 / 3) * Math.PI;
let colorList = ["#55e3fe", "#2f87fc", "#343eeb"];
let arcWidth = "30"; //圆轨尺寸
let arcRadius = 180; // 圆轨半径
let lineRadius = 140; //虚线半径
let startTrack = false; // 开始节点是否开始运动
let endTrack = false; // 结束节点是否开始运动
let canvas = document.querySelector("#test");
let ratioW = Math.round(canvas.width / parseInt(canvas.style.width)); // 宽度占比
let ratioH = Math.round(canvas.height / parseInt(canvas.style.height)); // 高度占比
let x0 = Math.round(canvas.width / 2); //中心x轴坐标
let y0 = Math.round(canvas.height / 2); //中心y轴坐标
let lrw = Math.round(arcWidth / 2);
var ctx = canvas.getContext("2d");
var grad = null; // 边框渐变渲染只能够进行线性渲染
let startNodeX = 0, //开始节点X轴坐标
startNodeY = 0, //开始节点Y轴坐标
endNodeX = 0, //结束节点X轴坐标
endNodeY = 0; //结束节点Y轴坐标
// 开始绘制
init();
// 画布鼠标按下事件
canvas.addEventListener("mousedown", function (e) {
let points = windowToCanvas(canvas, e.clientX, e.clientY);
let startDis = distance(points.x, points.y, startNodeX, startNodeY);
let endDis = distance(points.x, points.y, endNodeX, endNodeY);
if (lrw > startDis) {
startTrack = true;
ctx.setLineDash([3, 4]);
// 开始节点
ctx.beginPath();
ctx.lineWidth = lrw - 8 + "";
ctx.strokeStyle = "#ffffff";
ctx.arc(startNodeX, startNodeY, lrw - 5, 0, 2 * Math.PI);
ctx.stroke();
}
if (lrw > endDis) {
endTrack = true;
ctx.setLineDash([3, 4]);
// 结束节点
ctx.beginPath();
ctx.lineWidth = lrw - 8 + "";
ctx.strokeStyle = "#ffffff";
ctx.arc(endNodeX, endNodeY, lrw - 5, 0, 2 * Math.PI);
ctx.stroke();
}
});
// 画布鼠标松开事件
canvas.addEventListener("mouseup", function (e) {
if (startTrack) {
ctx.setLineDash([1, 0]);
// 开始节点
ctx.beginPath();
ctx.lineWidth = lrw + "";
ctx.fillStyle = colorList[0];
ctx.arc(startNodeX, startNodeY, lrw, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
}
if (endTrack) {
ctx.setLineDash([1, 0]);
// 结束节点
ctx.beginPath();
ctx.lineWidth = lrw + "";
ctx.fillStyle = colorList[2];
ctx.arc(endNodeX, endNodeY, lrw, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
}
startTrack = false;
endTrack = false;
});
// 画布鼠标移动事件
canvas.addEventListener("mousemove", function (e) {
// 节点运动轨迹
if (startTrack || endTrack) {
let points = windowToCanvas(canvas, e.clientX, e.clientY);
let angle = vectorAngle([0, 0], [x0 - points.x, y0 - points.y]);
// 开始节点
if (startTrack) start = angle;
// 结束节点
if (endTrack) end = angle;
init();
}
});
// 绘制画布函数
function init() {
startNodeX = Math.round(x0 + arcRadius * Math.cos(start)); //开始节点X轴坐标
startNodeY = Math.round(y0 + arcRadius * Math.sin(start)); //开始节点Y轴坐标
endNodeX = Math.round(x0 + arcRadius * Math.cos(end)); //结束节点X轴坐标
endNodeY = Math.round(y0 + arcRadius * Math.sin(end)); //结束节点Y轴坐标
if (start != end && startNodeX == endNodeX && startNodeY == endNodeY) {
grad = ctx.createLinearGradient(
startNodeX,
startNodeY,
x0 + (x0 - startNodeX),
y0 + (y0 - startNodeX)
);
} else {
grad = ctx.createLinearGradient(
startNodeX,
startNodeY,
endNodeX,
endNodeY
);
grad.addColorStop(0, colorList[0]);
grad.addColorStop(0.5, colorList[1]);
grad.addColorStop(1, colorList[2]);
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(0, 0); //坐标定位至0,0处
// 1.背景颜色
ctx.fillStyle = "#040e31";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.setLineDash([1, 0]);
// 2.底部透明圆环
ctx.beginPath();
ctx.lineWidth = arcWidth;
ctx.strokeStyle = "#ccdffb55";
ctx.arc(x0, y0, arcRadius, 0, 2 * Math.PI); //绘制一个完整的圆
ctx.stroke();
// 3.渐变圆弧
ctx.beginPath();
ctx.lineWidth = arcWidth;
ctx.strokeStyle = grad;
ctx.arc(x0, y0, arcRadius, start, end);
ctx.stroke();
// 4.灰色虚细线
ctx.beginPath();
ctx.lineWidth = "20";
ctx.setLineDash([3, 8]);
ctx.strokeStyle = grad;
ctx.arc(x0, y0, lineRadius, start, end);
ctx.stroke();
// 5.渐变虚细线
ctx.beginPath();
ctx.lineWidth = "20";
ctx.strokeStyle = "#ccdffb55";
// 设置虚线排列
ctx.arc(x0, y0, lineRadius, end, start); //绘制一个完整的圆
ctx.stroke();
// 6.画圆弧两端的图标
ctx.setLineDash([1, 0]);
// 开始节点
ctx.beginPath();
ctx.lineWidth = lrw + "";
ctx.fillStyle = colorList[0];
ctx.arc(startNodeX, startNodeY, lrw, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
// 结束节点
ctx.beginPath();
ctx.fillStyle = colorList[2];
ctx.arc(endNodeX, endNodeY, lrw, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath();
if (startTrack) {
ctx.setLineDash([3, 4]);
// 开始节点
ctx.beginPath();
ctx.lineWidth = lrw - 8 + "";
ctx.strokeStyle = "#ffffff";
ctx.arc(startNodeX, startNodeY, lrw - 5, 0, 2 * Math.PI);
ctx.stroke();
}
if (endTrack) {
ctx.setLineDash([3, 4]);
// 结束节点
ctx.beginPath();
ctx.lineWidth = lrw - 8 + "";
ctx.strokeStyle = "#ffffff";
ctx.arc(endNodeX, endNodeY, lrw - 5, 0, 2 * Math.PI);
ctx.stroke();
}
// 7.计算占比
// let percent = (((end - start) / (2 * Math.PI)) * 100).toFixed(2);
// ctx.font = `normal normal normal 32pt 宋体`;
// ctx.fillStyle = colorList[0];
// ctx.textAlign = "center";
// ctx.lineWidth = 1;
// ctx.textBaseline = "middle";
// ctx.fillText(percent + "%", x0, y0);
// 8.加载图片
// let img = new Image();
// img.src = "./images/circle.png";
// img.onload = function (e) {
// // ctx.clearRect(0, 0, canvas.width, canvas.height);
// ctx.drawImage(this, 100, 100, 600, 600);
// };
}
// 两点之间的距离
function distance(x1, y1, x2, y2) {
let squareSum = Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2);
return Math.round(Math.sqrt(squareSum));
}
// 求以下两个向量之间的角度
function vectorAngle(x, y) {
let angle = Math.atan2(x[1] - y[1], x[0] - y[0]);
return angle;
}
/** 获取鼠标在canvas上的坐标**/
function windowToCanvas(canvas, x, y) {
let rect = canvas.getBoundingClientRect();
return {
x: Math.round((x - rect.left * (canvas.width / rect.width)) * ratioW),
y: Math.round(
(y - rect.top * (canvas.height / rect.height)) * ratioH
),
};
}
</script>
</body>
</html>