起因
最近有一个需求,需要在一个环形图上面根据角度画一条标线。
类似这样:
第一个想法就是用三角函数算出起点和终点的坐标,无奈数学都还给老师了,就按照记忆里的三角函数知识补了补课。
但是落到代码上的时候发现,javascript Math.sin()
和 Math.cos()
的表现好像和预期不太一样?
弧度制
第一个问题是Math.sin(x:number):number
接收的参数 x 不是在 CSS 中常用的角度单位 Deg,是角度的弧度表示。
首先 度(deg) 和 弧度(rad) 都是平面角的单位。
单位弧度定义为圆弧长度等于半径时的圆心角。
根据公式圆的周长为 2πr。所以一个圆的弧度为 2π。
一个圆的角度是360°。所以 2πrad = 360°,πrad = 180°。1rad 也就等于 180°/π,约57.3°。
搞清楚了角度和弧度的转换,就可以使用前端比较熟悉的角度为单位来封装组件了。
三角函数的单位圆定义
知道了入参代表的含义,我们还需要知道Math.sin(x:number):number
返回值代表的含义。
Math.sin(x:number):number
方法返回一个 -1 到 1 之间的数值,想弄清楚这个数值代表的含义,需要知道一个知识点,三角函数的单位圆定义。
在直角三角形中,正弦、余弦以及其它三角函数只有当角度大于 0 且小于 π/2 (这里的 π/2 为弧度,即 90°) 时才有意义。
但是,在单位圆上,对于任意的实数角度,这些函数都有直观的意义。如图:
正弦函数与余弦函数可以用如下方法定义。
设圆心为(0,0),这里的 x、y 就是当圆的半径为 1 时,点以圆心为坐标系的坐标。
落到 canvas 上
环形图是使用图表库画的,不需要我操心。
但是想在 canvas 中画一条线,就需要知道起点和终点相对于 canvas 的计算机坐标系的坐标。
我们需要对三角函数的单位圆坐标系进行转换和平移。平移的过程如下。
- 首先要改变 y 轴方向,计算机坐标系的 y 轴正值朝下,圆的坐标系的 y 轴正值朝上,所以要 y = -y。
- 之后就是将坐标系平移到左上,x 和 y 都要加上圆心在计算机坐标系上的位置,得出的就是计算机坐标系的坐标。
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta
name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
/>
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<title>Document</title>
</head>
<body>
<canvas id="canvas" width="300" height="300"></canvas>
<div style="width: 300px; text-align: center" id="text"></div>
<script defer>
function renderMarkLine(deg) {
// 以 12 点钟方向为起点的顺时针角度,这样比较容易理解。
deg = deg || 0;
const canvasElement = document.getElementById("canvas");
const context = canvasElement.getContext("2d");
context.clearRect(0, 0, canvasElement.width, canvasElement.height);
const center = [150, 150];
const radius = 100;
context.strokeStyle = "#000";
context.lineWidth = 10;
// 画环
context.beginPath();
context.arc(center[0], center[1], radius, 0, 2 * Math.PI);
context.stroke();
// 画标线
const clockwiseDeg = (360 + (90 - deg)) % 360;
const clockwiseRad = (clockwiseDeg / 180) * Math.PI;
console.log(clockwiseDeg);
// 圆坐标系中的坐标
const markLineStart = [
radius * 0.8 * Math.cos(clockwiseRad),
radius * 0.8 * Math.sin(clockwiseRad),
];
const markLineEnd = [
radius * 1.2 * Math.cos(clockwiseRad),
radius * 1.2 * Math.sin(clockwiseRad),
];
// 计算机坐标系中的坐标
const canvasMarkLineStart = [
center[0] + markLineStart[0],
center[1] - markLineStart[1],
];
const canvasMarkLineEnd = [
center[0] + markLineEnd[0],
center[1] - markLineEnd[1],
];
// 绘制标线
context.beginPath();
context.strokeStyle = "red";
context.moveTo(...canvasMarkLineStart);
context.lineTo(...canvasMarkLineEnd);
context.stroke();
}
let deg = 0;
const textElement = document.getElementById("text");
function render() {
renderMarkLine(deg);
textElement.textContent = `deg: ${deg}`;
}
render();
setInterval(() => {
deg += 30;
render();
}, 1500);
</script>
</body>
</html>
参考资料: