探讨利用余弦定理和正切函数(tan)的性质,将角度转化为坐标变换,从而使得图形能够根据鼠标位置的变化而实现旋转。
⬜ 画一个矩形及对应旋转图标
一点小样式,我这里使用了Unocss,图标偷懒了,都可以自行实现和替换,文章重点不是这个,小问题,不要介意哈~
scss
<template>
<div class="relative h-screen flex flex-col items-center justify-center">
<div class="absolute h10 w10 border-1 border-black border-solid bg-blue text-center line-height-10">
<div i-carbon-sun dark:i-carbon-moon absolute class="h2 w2 -m-3" />
</div>
</div>
</template>
新增属性style="transform:rotate(45deg)"
给一个45度旋转角度看下效果:
可以看到矩形成功旋转了45度角,那么接下来我们需要做的就是在拖动旋转图标的时候动态计算rotate的数值。
🛆 根据余弦定理 <math xmlns="http://www.w3.org/1998/Math/MathML"> c 2 = a 2 + b 2 − 2 a b c o s σ c^2=a^2+b^2-2abcos\sigma </math>c2=a2+b2−2abcosσ
余弦定理变式: <math xmlns="http://www.w3.org/1998/Math/MathML"> c o s σ = ( a 2 + b 2 − c 2 ) / 2 a b cos\sigma = (a^2 + b^2 -c^2) / 2ab </math>cosσ=(a2+b2−c2)/2ab
勾股定理公式: <math xmlns="http://www.w3.org/1998/Math/MathML"> a 2 + b 2 = c 2 a^2 + b^2 = c^2 </math>a2+b2=c2
计算一个角度,我第一个反应就是用余弦定理,观察以下图形,根据余弦定理我们需要找到三角形的三边长才能计算出角度,鼠标点击时的起点到终点可以得到三角形的两条直角边,根据勾股定理就可以得到第三边长。
我们需要构建一个平面直角坐标系,首先就得找到原点,观察可知,这个原点即为矩形的中心点,图形围绕这个中心点进行旋转。
⚪ 计算中心点(旋转轴心、坐标系原点)
计算矩形的中心点,这里使用到了 Element.getBoundingClientRect()
方法返回一个 DOMRect
对象,其提供了元素的大小及其相对于视口的位置。
getBoundingClientRect
返回值是一个DOMRect
对象,是包含整个元素的最小矩形(包括padding
和border-width
)。该对象使用left
、top
、right
、bottom
、x
、y
、width
和height
这几个以像素为单位的只读属性描述整个矩形的位置和大小。除了width
和height
以外的属性是相对于视图窗口的左上角来计算的。
MouseEvent.clientX
:表示鼠标指针相对于浏览器窗口的横向坐标。坐标原点位于浏览器窗口的左上角,x 轴正方向向右延伸,单位是像素。MouseEvent.clientY
:表示鼠标指针相对于浏览器窗口的纵向坐标。坐标原点位于浏览器窗口的左上角,y 轴正方向向下延伸,单位是像素。
这两个属性通常在处理鼠标事件时被使用。例如,在拖拽、点击、绘图等场景下,可以使用这些属性来获取鼠标指针的位置。
arduino
class Point {
x = 0
y = 0
}
const origin = new Point()
function rotateMousedown(e: MouseEvent) {
const rect = rectRef.value?.getBoundingClientRect()
if (!rect)
return
origin.x = rect.left + rect.width / 2
origin.y = rect.top + rect.height / 2
}
↔ 计算两点间距离
当使用余弦定理时,我们需要知道三个边长或两边和一个夹角,以便计算第三边的长度。在这个情况下,我们可以使用鼠标位置与圆心之间的距离作为一个边,圆的半径作为第二个边,以及初始角度作为夹角。先封装一个计算两点间距离的函数:
javascript
/**
* 计算两点间距离
* @param p1
* @param p2
*/
function calculateDistance(p1: Point, p2: Point) {
return Math.sqrt((p2.x - p1.x) ** 2 + (p2.y - p1.y) ** 2)
}
💠 公式计算
鼠标按下记录初始位置,移动的时候记录移动后的位置,然后,我们可以使用余弦定理来计算新的鼠标位置与圆心之间的距离,从而确定旋转角度。
arduino
function rotateMousedown(e: MouseEvent) {
const rect = rectRef.value?.getBoundingClientRect()
if (!rect)
return
origin.x = rect.left + rect.width / 2
origin.y = rect.top + rect.height / 2
initialDistance = calculateDistance(origin, { x: e.clientX, y: e.clientY })
const mousemoveHandler = (e: MouseEvent) => {
const newDistance = calculateDistance(origin, { x: e.clientX, y: e.clientY })
// cosTheta = (a^2 + b^2 -c^2) / 2ab
const cosTheta
= (initialDistance ** 2 + rect.width ** 2 - newDistance ** 2)
/ (2 * initialDistance * rect.width)
// 余弦值转为弧度再转为角度
rotationAngle.value = Math.acos(cosTheta) * (180 / Math.PI)
}
const mouseupHandler = () => {
document.removeEventListener('mousemove', mousemoveHandler)
document.removeEventListener('mouseup', mouseupHandler)
}
document.addEventListener('mousemove', mousemoveHandler)
document.addEventListener('mouseup', mouseupHandler)
}
测试
折腾半天,满怀激动的打开了页面:
晴天霹雳,一卡一卡的,完全不能够使用...
思绪良久,打开搜索引擎一顿操作,决定改换思路,使用正切函数(tan)来计算旋转角度。
🔻 根据正切函数计算
Math.atan2()
返回从原点 (0,0) 到 (x,y) 点的线段与 x 轴正方向之间的平面角度 (弧度值),也就是 Math.atan2(y,x)。
具体思路也是先计算中心点,然后根据Math.atan2来计算初始角度,鼠标移动过程中计算旋转角度的差值:
javascript
/**
* 计算鼠标位置与圆心的角度
* @param p1
* @param p2
*/
function calculateAngle(p1: Point, p2: Point) {
return Math.atan2(p2.y - p1.y, p2.x - p1.x) * (180 / Math.PI)
}
- 完整代码
xml
<script setup lang="ts">
import { ref } from 'vue'
class Point {
x = 0
y = 0
}
const rectRef = ref<HTMLDivElement>()
const rotationAngle = ref(0)
const origin = new Point()
let initialAngle = 0
/**
* 计算鼠标位置与圆心的角度
* @param p1
* @param p2
*/
function calculateAngle(p1: Point, p2: Point) {
return Math.atan2(p2.y - p1.y, p2.x - p1.x) * (180 / Math.PI)
}
function rotateMousedown(e: MouseEvent) {
const rect = rectRef.value?.getBoundingClientRect()
if (!rect)
return
origin.x = rect.left + rect.width / 2
origin.y = rect.top + rect.height / 2
// 计算初始角度
initialAngle = calculateAngle(origin, { x: e.clientX, y: e.clientY })
const mousemoveHandler = (e: MouseEvent) => {
// 计算鼠标位置与圆心的角度
const currentAngle = calculateAngle(origin, { x: e.clientX, y: e.clientY })
// 计算旋转角度的差值
rotationAngle.value = currentAngle - initialAngle
}
const mouseupHandler = () => {
document.removeEventListener('mousemove', mousemoveHandler)
document.removeEventListener('mouseup', mouseupHandler)
}
document.addEventListener('mousemove', mousemoveHandler)
document.addEventListener('mouseup', mouseupHandler)
}
</script>
<template>
<div class="relative h-screen flex flex-col items-center justify-center">
<div
ref="rectRef"
class="absolute h10 w10 border-1 border-indigo border-solid bg-blue text-center line-height-10"
:style="{ transform: `rotate(${rotationAngle}deg)` }"
>
<div
i-carbon-sun
dark:i-carbon-moon
absolute
class="h2 w2 -m-3"
@mousedown="rotateMousedown"
/>
</div>
</div>
</template>
测试
测试效果比使用余弦定理的方法好了很多,非常的丝滑。
最后
完整例子源码我放在github上了,作为记录学习使用,链接article-drag-rotation