本文覆盖面基本在移动端为主,先来一段初高中中知识-三角函数、弧度制、向量点积与叉积回顾。
角度弧度知识点
角度和弧度转换公式:
弧度 = 角度 * PI / 180 或 2 * PI / 360
角度 = 弧度 * 180 / PI
角度与弧度互转:
- 角度定义
两条射线从圆心向圆周射出,形成一个夹角和夹角正对的一段弧。当弧长正好等于圆周长的360分之一时,两条射线的夹角的大小为1度。(单位: º) - 弧度定义
两条射线从圆心向圆周射出,形成一个夹角和夹角正对的一段弧。当这段弧长正好等于圆的半径时,两条射线的夹角大小为1弧度(单位:rad)。
可简单理解为:弧度 = 弧长 / 半径 - 弧长与弧度
3.1 圆的周长C的计算公式为:C = 2πr = πd (r - 半径;d - 直径)
3.2 圆一周的弧长为:2πr (弧长 = 周长)
3.2 圆一周的弧度为:2πr / r = 2π (根据:弧度 = 弧长 / 半径) - 度与角度的转换
根据圆为360 º,弧度为2π,即 360º = 2π
4.1 角度转弧度:2π / 360 = π / 180 ≈ 0.0174rad, 即: 度数 * (π / 180) = 弧度
例如:将30º转为弧度rad
30º * (π / 180)= 0.523320 rad
4.2 弧度转角度: 360 / 2π = 180 / π ≈ 57.3º, 即: 弧度 * (180 / π) = 度数
例如:将0.523320rad转为度º
0.523320rad * (180 / π) = 29.9992352688º
三角函数知识点
三角形用符号“△”表示,它是由三条线段组成的封闭几何图形(图1-1),组成三角形的线段叫做三角形的边,相邻两边的公共端点叫做三角形顶点,相邻两边所组成的角为三角形内角。三角形三个角的大小不一定相等,但三角形内角的和总是等于180。
由于函数本身的意义就是互相依赖的变量,它在直角三角形中同样是某角的应变数,它随角度的变化而变化。一个直角三角形(图1-1a),如果知道了其中的任意两个边,那就可以知道锐角∠A或∠B的角度大小,同理,如果知道任意一个锐角和一条边,也可以得到其他两条边的长短尺寸。
在直角三角形中,它的三角函数有六种,其定义和计算公式见下表。计算时,正弦、正切和正割的函数值随角度的增大而增大,但不是和角度成正比例关系,也就是说,角度增大一倍,函数不是增加一倍;相反,余弦、余切和余割的函数值是随角度的增大而减小,同样,也不成比例关系,就是说角度增加一倍,函数值不是相应减小一倍。
在实际应用中,只要记住正弦、余弦、正切的函数公式就可以了,因为正弦和余割、余弦和正割、正切和余切互为倒数关系,即:
下面来熟悉几个 JavaScript Math 对象函数
Math.acos()函数接受一个数字,并以弧度返回其反余弦值。要将结果值转换为度数,请将其乘以180,然后将结果除以3.14159(pi值)。Math.sqrt()函数返回一个数的平方根。Math.sin()函数返回一个数值的正弦值。// 返回值在 -1.0 到 1.0 之间;Math.cos()函数返回一个数值的余弦值。// 返回值在 -1.0 到 1.0 之间;Math.atan2函数返回从原点 (0,0) 到 (x,y) 点的线段与 x 轴正方向之间的平面角度 (弧度值)
正文开始,在移动端画布中旋转缩放操作图片时,已知向量touchVector在事件onTouch初始化时存储了向量值:
{ x: e.touches[1].pageX - e.touches[0].pageX, y: e.touches[1].pageY - e.touches[0].pageY }
在onTouchMove时存储旋转后的向量changedVector:
{ x: e.touches[1].pageX - e.touches[0].pageX, y: e.touches[1].pageY - e.touches[0].pageY }
如下图所示,初始与旋转的坐标点标记可以看做一个平面直角坐标系:
平面内一个直角坐标系XOY,经过平移、顺时针旋转θ角度后形成新的直角坐标系X'O'Y',已知O'在XOY坐标系中的坐标为(Xo,Yo),点M在XOY坐标系中的坐标为(Xm,Ym),求M在X'O'Y'坐标系中的坐标(x',y')。
最后附一个知识点,叉积与点积
先介绍一下点积的基本求解形式2,如果给定两个向量a和b:
则它们的点积定义为:
其实就是两个向量的每个变量两两相乘,再相加。
点积的几何意义也很简单:向量b在a上面的投影。注意,这里也说明了点积的结果为常量,而非向量。
图上红色部分即为v(3,4)在v(8,0)上面的投影。
叉积的一般计算求解需要用到矩阵乘法,而后面涉及的几何求交主要是二维坐标,所以这里直接介绍二维坐标的乘法:即x和y交叉相乘,再相减。
叉积的几何意义相对复杂一些:1)其模等于两个向量所组成的平行四边形的面积;2)方向为垂直于两个向量所在平面。所以叉积和点积不一样,叉积的结果是向量,而不是常量。大家可以看下面图示2来理解。
思路缕清后就可以整理代码逻辑,附下代码片段简要说明
//Math.sqrt可以获取到两个点之间的距离
function getDistance(xLen, yLen) {
return Math.sqrt(xLen * xLen + yLen * yLen);
}
// 判断旋转方向 1:顺时针 -1:逆时针,原理是叉积,一般计算求解用到的矩阵乘法,二维坐标的乘法即x和y交叉相乘再相减,叉积的结果是向量而不是常量
function getRotateDirection(vector1, vector2) {
return vector1.x * vector2.y - vector2.x * vector1.y;
}
// 平面两个向量的点积
function dot(vector1, vector2) {
return vector1.x * vector2.x + vector1.y * vector2.y
}
// 计算两次手势状态的夹角
function getAngle(vector1, vector2) {
// 模积
const mr = getDistance(vector1.x, vector1.y) * getDistance(vector2.x, vector2.y)
if (mr === 0) return 0
// 注:平面夹角的余弦值等于两个平面法向量的点积除以它们的模乘积
let r = dot(vector1, vector2) / mr
if (r > 1) r = 1
if (r < -1) r = -1
return Math.acos(r)
}
// 根据onTouch和onTouch缓存的坐标值来计算角度值
function getRotateAngle(vector1, vector2) {
let angle = getAngle(vector1, vector2)
if (getRotateDirection(vector1, vector2) > 0) angle *= -1
return (angle * 180) / Math.PI
}
// 角度转换
function snapToAngle(rotation, angle, distance_angle) {
rotation = rotation % 360;
if (rotation < 0) {
rotation = 360 + rotation;
}
var yu = rotation % angle;
if (yu > 0 && yu < distance_angle) {
rotation = rotation - yu;
}
var tmp = angle - yu;
if (tmp > 0 && tmp < distance_angle) {
rotation = rotation + tmp;
}
if (rotation > 180) {
rotation = rotation - 360
}
return parseInt(rotation);
}
由上方法计算出角度后,在onTouchMove事件中我们可以根据getRotateAngle计算出:
// 初始化时设置的图像对象数据
const currentImgData = {
rotate: 0, // 假设图像初始旋转角度都为0°
startRotate: 0 // 记录上一次的旋转度数
}
let angel = getRotateAngle(changedVector, touchVector);
// 或者直接用Math.atan2计算
let angel = Math.atan2(changedVector.y, changedVector.x) - Math.atan2(touchVector.y, touchVector.x)
currentImgData.rotate: snapToAngle(currentImgData.startRotate + angel, 90, 5)
附参考资料