第三章 三角函数与圆周运动

说实话,很多前端同学一看到「三角函数」就头疼。我当年也一样。

但后来我发现,三角函数其实就是描述「转圈圈」的数学工具。你想想看,钟表指针在转,车轮在转,地球在转——这些运动背后,全是三角函数在撑腰。

这一章,我们就把它彻底搞明白。

3.1 正弦、余弦、正切:三个好兄弟

先看一个单位圆。半径为1,圆心在原点。

当一条半径从水平方向开始逆时针旋转,它和圆相交的点,坐标就是 (cosθ, sinθ)。

  • sinθ = 对边 / 斜边 = 点的 y 坐标
  • cosθ = 邻边 / 斜边 = 点的 x 坐标
  • tanθ = 对边 / 邻边 = sinθ / cosθ

我刚开始做动画时,总记混 sin 和 cos。后来我给自己编了个口诀:「sin 是竖着的,cos 是横着的」。嗯,虽然土,但管用。

核心记忆点:

  • sin 控制上下(y轴)
  • cos 控制左右(x轴)
  • tan 控制斜率(用得少,但做渐变效果时有用)

3.2 函数图像长什么样?

把角度从 0 到 2π 画出来,sin 和 cos 的图像就像波浪一样起伏。

  • sin 图像:从 0 开始,先升到 1,再降到 -1,最后回到 0。像个平滑的波浪。
  • cos 图像:从 1 开始,降到 -1,再升回 1。和 sin 差了一个 π/2 的相位。
  • tan 图像:这个比较「暴躁」。在 π/2 和 3π/2 处有垂直渐近线,值会突然跳到无穷大。

我在项目中做过一个「呼吸灯」效果,用的就是 sin 函数。灯光的亮度在 0.3 到 1.0 之间平滑变化,看起来就像真的在呼吸。说白了,就是 brightness = 0.65 + 0.35 * sin(time) 这么简单。

避坑指南: 我曾经在 tan 函数上栽过跟头。做旋转木马效果时,直接用 tan 控制角度,结果在 π/2 附近画面直接「飞」了。后来我改用 sin 和 cos 组合,才稳下来。记住:tan 不适合做连续动画。

3.3 弧度制 vs 角度制

为什么前端里都用弧度?因为 Math 库只认弧度。

角度 弧度 sin cos
0 0 1
30° π/6 0.5 0.866
45° π/4 0.707 0.707
60° π/3 0.866 0.5
90° π/2 1 0
180° π 0 -1
360° 0 1

转换公式很简单:

// 角度转弧度
const toRadians = (deg) => deg * Math.PI / 180;

// 弧度转角度
const toDegrees = (rad) => rad * 180 / Math.PI;

我个人习惯在代码里统一用弧度。只在调试时,才把弧度转成角度看看数值是否合理。

3.4 平滑往复运动:三角函数的拿手好戏

你想想看,一个元素从 A 点移动到 B 点,再回到 A 点,如此循环——这不就是 sin 或 cos 的波形吗?

核心公式就一行:

// 平滑往复运动
const value = center + amplitude * Math.sin(frequency * time + phase);
  • center:运动中心位置
  • amplitude:振幅(摆动幅度)
  • frequency:频率(控制快慢)
  • phase:相位(控制起始位置)
  • time:时间变量(通常用 requestAnimationFrame 累加)

举个例子,让一个小球在屏幕上左右摆动:

let time = 0;

function animate() {
  time += 0.02;
  
  const x = 400 + 200 * Math.sin(time);
  const y = 300;
  
  ball.style.transform = `translate(${x}px, ${y}px)`;
  requestAnimationFrame(animate);
}

animate();

这段代码里,小球会在 200px 到 600px 之间来回摆动。频率由 time += 0.02 控制,数值越大摆动越快。

实战经验: 我在做「加载动画」时,经常用这个公式。比如三个点依次弹跳,每个点加上不同的相位偏移:

// 三个点依次弹跳
const phases = [0, Math.PI * 2 / 3, Math.PI * 4 / 3];

dots.forEach((dot, i) => {
  const y = 100 + 30 * Math.sin(time + phases[i]);
  dot.style.transform = `translateY(${y}px)`;
});

这样三个点就会像波浪一样依次起伏,视觉效果非常流畅。

3.5 组合运动:让动画更丰富

单个 sin 只能做直线往复。但把 sin 和 cos 组合起来,就能画出圆、椭圆甚至更复杂的轨迹。

// 圆周运动
const radius = 150;
const x = centerX + radius * Math.cos(time);
const y = centerY + radius * Math.sin(time);

为什么是 cos 控制 x,sin 控制 y?因为 cos 从 1 开始,sin 从 0 开始。这样物体从右侧开始,逆时针画圆。

如果想画椭圆,只需要给 x 和 y 不同的半径:

const x = centerX + 200 * Math.cos(time);
const y = centerY + 100 * Math.sin(time);

这样就是一个横向拉长的椭圆轨迹。

注意: 做圆周运动时,time 的增量要控制好。增量太大,动画会「跳帧」;增量太小,运动太慢。我一般用 0.01 到 0.05 之间,具体看效果微调。

3.6 实际项目中的避坑指南

我曾经在一个 H5 活动中,用三角函数做「波浪文字」效果。每个字上下浮动,看起来像海浪一样起伏。

一开始我直接用 Math.sin(time + index * 0.5),结果文字抖动得很厉害。后来发现是时间增量不均匀导致的。

解决方案:用 performance.now() 获取精确时间,而不是用累加器。

let startTime = performance.now();

function animate() {
  const elapsed = (performance.now() - startTime) / 1000;
  
  letters.forEach((letter, i) => {
    const y = 50 + 20 * Math.sin(elapsed * 2 + i * 0.8);
    letter.style.transform = `translateY(${y}px)`;
  });
  
  requestAnimationFrame(animate);
}

这样不管帧率怎么变,动画始终是平滑的。

小技巧: 调试三角函数动画时,可以在控制台打印 Math.sin(time) 的值。如果数值在 -1 到 1 之间稳定变化,说明公式没问题。如果突然跳到 NaN,八成是 time 变成了 Infinity。

3.7 本章小结

三角函数其实没那么可怕。记住三点:

  • sin 和 cos 是「波浪」,适合做平滑往复
  • 弧度是前端唯一的选择,学会转换
  • 组合 sin 和 cos 能画出各种轨迹

下一章,我们会用这些知识做更复杂的动画效果。到时候你会发现,三角函数就是前端动画的「万能钥匙」。