三角函数在动画中的应用

1,482 阅读6分钟

勾股定理

勾股定理是一个基本的几何定理,指直角三角形的两条直角边的平方和等于斜边的平方

h^2 = a^2 + b^2

`

三角函数

什么是三角函数呢?我给他了一个简单的定义:所谓三角函数,在几何上来说就是夹角与边的关系!为了更直观,也为了让忘记的同学回忆起来,这里我给个示意图:

在上图中我示例出了几个常用的三角函数,角度与边(x, y和R)之间的关系!那么在canvas中角度与边之间的关系是怎样的呢?

如图所示,普通坐标与canvas坐标是不同的,canvas坐标以左上角作为坐标原点,y轴朝下为正。

那么坐标不同,对应的角度表示就会有所差异,这个差异主要体现在角度的正负,千言万语不如一图:

好了,这就是在canvas中角度的正负表示。

前面我们简单的介绍了三角函数的表示方法,知道了三角函数表示的是角度与边之间的关系,但是在实际开发中我们不仅想要通过角度来推出两边的距离长度比值,而更关心的是如何通过已知的距离(因为坐标的位置很好确定)来推出角度。那么,应该如何做呢?这里我们要用到反三角函数

那么对应到,javascript中的相应表示方法是什么呢?这里做个小的归纳:

这里需要强调的是在canvas中采用的是弧度制。这样你就可以理解 θ * Math.PI/180是将角度转成弧度,比如:30° = 30 * π /180 = π / 6。 而将弧度转成角度自然就要用弧度值Math.asin(x/R) 乘上180/Math.PI。

极坐标系和单位圆

在笛卡尔直角坐标系中,任一点 (x, y) 都可以转化成极坐标表示 (r, θ),其中

r = Math.sqrt(x^2 + y^2)
θ = Math.atan2(y, x)

单位圆的定义是半径为单位长度的圆,圆上任意一点的横坐标就是对应角度的余弦值,任意点的纵坐标就是对应角度的正弦值。

Math.atan2(dy, dx)

有一个重点要讲的函数,Math.atan(...)这个函数,他可以直接通过两个直角边得到角度值,相比于其他两个需要通过计算长边来得到角度值来说,是不是更酷了!但是它有个问题,因为这个arctan这个函数的特性(这里就不细讲了)它会导致一个很重要的问题,上图:

简单的说,就是使用Math.atan(...)你会得到两个相同的角度值,而电脑是无法判断你到底是转的哪个角度!!!这时,另一个函数就横空出世了,当当当当,他就是Math.atan2(dy, dx)!他不仅解决了上面我们说的问题,而且只需要传入两个横纵坐标距离!是不是很酷。

图像变换

简单的图像变换 以正弦曲线为例,对函数进行简单的变换,得到不一样的结果。

正弦曲线公式:y = A sin(Bx + C) + D

A 控制振幅,A 值越大,波峰和波谷越大,A 值越小,波峰和波谷越小;

B 值会影响周期,B 值越大,那么周期越短,B 值越小,周期越长。

C 值会影响图像左右移动,C 值为正数,图像右移,C 值为负数,图像左移。

D 值控制上下移动。

这个公式非常有用,如果下文的代码让你不解,记得回来查看注解。

简单得回顾一下之后,确保你还记得这些基础知识,那么这些曾经被得滚瓜烂熟的内容,和前端代码结合会变成什么样?

常见的应用场景

图像应用

Wave曲线

最直观的应用是使用三角函数来绘制 Wave 曲线

for (let x = 0; x < width; x++) {
  const y = Math.sin(x * a) * amplitude
}

曲线绘制完,这时曲线是静态的,如何让它动起来?可以通过不断改变水平偏移(xOffset),让曲线水平移动,即可产生动态的效果。

再结合三角函数偏移让左右成为波谷,中间成为波峰,就能得到曼妙的波纹。 (

for (let x = 0; x < width; x++) {
  const radians = x / width * Math.PI * 2
  const scale = (Math.sin(radians - Math.PI * 0.5) + 1) * 0.5
  const y = Math.sin(x * 0.02 + xSpeed) * amplitude * scale
}

水波图 我们已经了解了正弦曲线的一些属性,我们可以把这些属性来控制波浪。

振幅:控制波浪的高度

周期:控制波浪的宽度

相移:控制波浪的水平移动

垂直位移:控制水位的高度

动画效果的实现主要是利用相移,通过不断水平移动曲线,产出波浪移动的感觉,然后可以绘制多条曲线,曲线之间通过控制属性(高度、宽度、移动速度),产生视觉差,就会有波浪起伏的感觉了。

根据定义波浪的参数配置,通过公式: y = 波浪高度 * sin(x * 波浪宽度 + 水平位移),来绘制正弦曲线

水位控制

水位控制,也就是映射到数据的百分比。 我们来看看:y = A sin(Bx + C) + D,曲线的高度有 A 和 D 决定,A 控制波浪的高度,实际水位还是由 D 来控制。 水位的高度,在可视化上含义就是数据的百分比,假设目前的百分比80%,水位的高度就 canvasHeight * 0.8,映射到坐标系统 y 的坐标就是 canvasHeight * (1 - 0.8)。(坐标原点在左上角)。

SlowInSlowOut

正余弦曲线有很自然地缓入缓出的特性,并且在一个周期里面从 -1 到 1 再回到 -1,非常适合用来模拟一些物理效果。因为真实世界里面,汽车都是缓慢启动,加速,减速,再缓慢减速直到速度变为 0 的,突变会让人很难受。左边的摆球是线性匀速摆动,右边是用了三角函数优化的结果。

只需使用 sin 或 cos 乘以最大角度,就可以得到在摆动最大角度之间的 SlowInSlowOut。

ctx.rotate(Math.cos(t / 180 * Math.PI) * Math.PI * 0.25)
角度控制

在开发过程中,我们有时候会跟角度打交道,比如在头像左上角(45deg)显示 Notification 红点,用鼠标控制 rotation 等等。 运用三角函数,控制坐标的旋转,公式:

dx = mouse.x - object.x; 
dy = mouse.y - object.y; 
object.rotation = Math.atan2(dy,dx); 

CSS三角函数动画

三角函数相关的动画并不一定需要用 JS 来写,比如下面的 DEMO,使用CSS,同样可以做到灵活控制在特定角度的动画(千万不要手写各个点的坐标!!!后期没办法维护。)

.popping-menu {
  .checkbox {
    display: none;
  }
  .checkbox:checked {
     ~ button {
      $per = 180 / 4;
      for $i in 1 2 3 4 5 {
        &:nth-of-type({$i}) {
          $angle = $per * ($i - 1) * 1deg + 180deg;
          $x = cos($angle) * $d;
          $y = sin($angle) * $d;
          opacity: 1;
          transition-delay: 0.1s * $i;
          transform: translate($x, $y);
        }
      }
    }
  }
}

源码

github.com/littlepurpl…

参考