在cavans中,凡是涉及角度都用“弧度”表示,例如180度就应该写成Math.PI,e而360度就该写成Math.PI*2,
在实际开发中,推荐一下写法:
度数*Math.PI/180
CANVAS使用的是W3C坐标系:
三角函数公式:
Math.atan的弊端:
tan度=X/Y;
坐标轴有四个像限:
X/Y=-X/-Y
-X/Y=X/Y
这种情况会导致一个tan度对应两个角度的情况,如图(图1)
tan(A)=-0.5(-1/2);
tan(B)=0.5(1/2);
tan(C)=-0.5(-1/2);
tan(D)=-0.5(-1/-2);
Math.atan无法区分26.57对应的是哪个夹角
使用Math.atan2,可以求出俩边之间夹角的度数并且判断该度数对应的是哪个夹角
Math.atan2接收两个参数,Math.atan接受的是一个参数【度数】
Math.atan2(1,2)=26.56 ,对应(图1)角B
Math.tan2(-1,-2)=-153.34...,对应(图1)角D
-153.34这个角度是从X轴正方形以逆方形计算的,这样就区分开了两个角度,如下图(图2)
由于Math.atan低弊端导致比较扫用,Math.atan2用的比较多
在线demo:http://runjs.cn/code/wsu1pqg6
源码:
绘制箭头:
function Arrow(x, y, color, angle) {
this.x = x || 0;
this.y = y || 0;
this.color = color || "#FF0099";
this.angle = angle || 0;
}
Arrow.prototype = {
stroke: function(cxt) {
cxt.save();
cxt.translate(this.x, this.y);
cxt.strokeStyle = this.color;
cxt.beginPath();
cxt.moveTo(-20, -10);
cxt.lineTo(0, -10);
cxt.lineTo(0, -20);
cxt.lineTo(20, 0);
cxt.lineTo(0, 20);
cxt.lineTo(0, 10);
cxt.lineTo(-20, 10);
cxt.closePath();;
cxt.stroke();
cxt.restore();
},
fill: function(cxt,cnv) {
cxt.save();
//cnv.clearReact(cnv.width,cnv.height)
cxt.translate(this.x, this.y);
cxt.rotate(this.angle);
cxt.beginPath();//切记,不要遗留这个
cxt.moveTo(-20, -10);
cxt.lineTo(0, -10);
cxt.lineTo(0, -20);
cxt.lineTo(20, 0);
cxt.lineTo(0, 20);
cxt.lineTo(0, 10);
cxt.lineTo(-20, 10);
cxt.closePath();
cxt.fill();
cxt.restore();
}
}
提供鼠标位置:
window.tools = {};
window.tools.getMouse = function(element) {
var mouse = { x: 0, y: 0 };
element.addEventListener("mousemove", function(e) {
var x, y;
var e = e || window.event;
if (e.pageX || e.pageY) {
x = e.pageX;
y = e.pageY;
} else {
//IE8以下,以及混子模式下的chrome和safari
x = e.clientX + document.body.scrollLeft || document.documentElement.scrollLeft;
y = e.clientY + document.body.scrollTop || document.documentElement.scrollTop;
}
//将当前的坐标减去canvas元素的偏移量,则为x、y为是鼠标在canvas中的相对坐标
x -= element.offsetLeft;
y -= element.offsetTop;
mouse.x = x;
mouse.y = y;
});
return mouse;
}
核心代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<canvas id="canvas" width="800" height="800"></canvas>
<script src="arrow.js"></script>
<script src="tools.js"></script>
<script>
function $$(id) {
return document.getElementById(id);
}
window.onload = function() {
var cnv = $$("canvas");
var cxt = cnv.getContext('2d');
//实例化一个箭筒,中心坐标为画布的中心坐标
var arrow = new Arrow(cnv.width / 2, cnv.height / 2);
var mouse = window.tools.getMouse(cnv);;
function drawFrame() {
window.requestAnimationFrame(drawFrame, cnv);
cxt.clearRect(0, 0, 800, 800);
//获取鼠标坐标
var dx = mouse.x - cnv.width / 2;
var dy = mouse.y - cnv.height / 2;
//获取鼠标和箭头的夹角
arrow.angle = Math.atan2(dy, dx);
arrow.fill(cxt);
};
drawFrame();
}
</script>
</body>
</html>
======更新2024/04/30========
Math.atan2拿到的是弧度
需要注意的是,它的取值范围是[-PI, PI]。
- 当 (x1, y1) 在第一象限, 0 < θ < PI/2
- 当 (x1, y1) 在第二象限 PI/2 < θ≤PI
- 当 (x1, y1) 在第三象限, -PI < θ < -PI/2
- 当 (x1, y1) 在第四象限, -PI/2 < θ < 0
弧度和角度转换公式
角度 = 弧度 * 180 / Math.PI;
弧度= 角度 * Math.PI / 180;
参考:https://www.cnblogs.com/webhmy/p/9700079.html
请注意,当使用JavaScript的
Math.atan2
函数来获取角度时,该函数返回的是一个弧度值,范围在-π到π之间(即-180度到180度),并且它考虑了符号(即方向),而且计算出来的角度是符合css transform rotate旋转规范的
图解
atan2计算出来的角度是符合css transform rotate旋转规范的
在线demo:JS Bin - Collaborative JavaScript Debugging
代码
<!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>
* {
margin: 0;
}
.box {
width: 50px;
height: 50px;
position: fixed;
top: 40px;
right: 40px;
background: red;
opacity: .5;
z-index: 10;
}
.box::after {
content: "";
width: 10px;
height: 10px;
background-color: black;
right: 0;
top: 0;
position: absolute;
display: block;
}
.tip {
position: fixed;
top: 10;
right: 10px;
z-index: 20;
}
</style>
</head>
<body>
<div class="box">
</div>
<div class="tip">Math.atan2计算出来的角度是符合css规范的</div>
<canvas />
<script>
const canvas = document.querySelector("canvas");
var ctx = canvas.getContext("2d");
const wWidth = document.documentElement.clientWidth;
const wheight = document.documentElement.clientHeight;
const xAxiosCenter = wWidth / 2;
const yAxiosCenter = wheight / 2;
canvas.width = wWidth;
canvas.height = wheight;
const pointR = 10;
const baseLayout = function () {
//中心园点
ctx.beginPath();
ctx.arc(xAxiosCenter, yAxiosCenter, pointR, 0, 2 * Math.PI);
ctx.fillStyle = "#ff0";//设置填充颜色
ctx.fill();//开始填充
ctx.stroke();
//水平中心线
ctx.moveTo(0, yAxiosCenter);
ctx.lineTo(wWidth, yAxiosCenter);
ctx.stroke();
//垂直中心线
ctx.moveTo(xAxiosCenter, 0);
ctx.lineTo(xAxiosCenter, wheight);
ctx.stroke();
}
baseLayout();
const box = document.querySelector(".box");
document.onmousedown = (e) => {
console.log(e.clientX, e.clientY)
ctx.clearRect(0, 0, wWidth, wheight);
baseLayout();
//绘制鼠标位置远点
ctx.beginPath();
ctx.arc(e.clientX, e.clientY, pointR, 0, 2 * Math.PI);
ctx.fillStyle = "red";//设置填充颜色
ctx.fill();//开始填充
ctx.stroke();
//绘制交叉点到鼠标的位置
ctx.moveTo(xAxiosCenter, yAxiosCenter);
ctx.lineTo(e.clientX, e.clientY);
ctx.stroke();
ctx.font = "15px serif";
//拿到的是弧度
const tan2Value = Math.atan2(e.clientY - yAxiosCenter, e.clientX - xAxiosCenter)
//角度要转
const rotate = tan2Value * 180 / Math.PI;
ctx.strokeText("弧度:" + tan2Value + ",角度:" + rotate, e.clientX + pointR, e.clientY + pointR);
box.style.transform = `rotate(${rotate}deg)`;
}
</script>
</body>
</html>