从余弦定理到正切函数:探索图形旋转的数学奥秘与实现方法

探讨利用余弦定理和正切函数(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的数值。

🛆 根据余弦定理 c 2 = a 2 + b 2 − 2 a b c o s σ c^2=a^2+b^2-2abcos\sigma c2=a2+b2−2abcosσ

余弦定理变式: c o s σ = ( a 2 + b 2 − c 2 ) / 2 a b cos\sigma = (a^2 + b^2 -c^2) / 2ab cosσ=(a2+b2−c2)/2ab

勾股定理公式: a 2 + b 2 = c 2 a^2 + b^2 = c^2 a2+b2=c2

计算一个角度,我第一个反应就是用余弦定理,观察以下图形,根据余弦定理我们需要找到三角形的三边长才能计算出角度,鼠标点击时的起点到终点可以得到三角形的两条直角边,根据勾股定理就可以得到第三边长。

我们需要构建一个平面直角坐标系,首先就得找到原点,观察可知,这个原点即为矩形的中心点,图形围绕这个中心点进行旋转。

⚪ 计算中心点(旋转轴心、坐标系原点)

计算矩形的中心点,这里使用到了 Element.getBoundingClientRect() 方法返回一个 DOMRect 对象,其提供了元素的大小及其相对于视口的位置。

getBoundingClientRect 返回值是一个 DOMRect 对象,是包含整个元素的最小矩形(包括 paddingborder-width)。该对象使用 lefttoprightbottomxywidthheight 这几个以像素为单位的只读属性描述整个矩形的位置和大小。除了 widthheight 以外的属性是相对于视图窗口的左上角来计算的。

  • 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

相关推荐
独泪了无痕8 小时前
Vue3 Hooks使用实战解析
前端·vue.js
shawxlee9 小时前
vue3在public下封装config.js自定义配置动态数据,可在打包后直接修改,方便后端部署及后续维护
前端·javascript·经验分享·vue·团队开发·js·项目优化
用户0595401744610 小时前
把记忆存储的回归测试从手工换成 Playwright + GitHub Actions,线上缺陷降低 80%
前端·css
触底反弹10 小时前
🚀 从 DOM0 级到 React 合成事件:前端事件监听的 20 年演进史
前端·react.js·面试
kyriewen10 小时前
我给前端项目的接口请求套了6层保护——才发现以前一直在裸奔
前端·javascript·面试
颜酱10 小时前
04 | 召回前置准备:搭好召回所需的四个数据库
前端·人工智能·后端
郝亚军11 小时前
如何安装webstorm、Node.js和vue CLI
前端·javascript·vue.js
IT_陈寒11 小时前
React的useEffect依赖项把我坑惨了
前端·人工智能·后端
东方小月12 小时前
从零开发一个Coding Agent:monorepo项目搭建
前端·后端·node.js
葬送的代码人生12 小时前
别再让 AI 瞎写代码了!Vibe Coding 三步法教你写出靠谱代码
前端·设计模式·架构