为啥都说CSS三角函数是“最讨厌”的特性,反三角函数真那么难搞?

2,256字
10–14 分钟
in

想要搞明白CSS里的asin()acos()atan()atan2()这些反三角函数到底有啥用,先得从三角函数那点事儿唠起。之前咱聊过sin()cos()tan(),它们就像是一个翻译官,给一个角度,就能返回一个比值。但现实里经常碰到反过来的时候:手头有个比值,比如两条边的长度,就想知道那个角度是多少度,这活儿就得反三角函数来干了。

目录

从比值挖出那个角

acos()cos()的逆向操作,asin()sin()的逆向操作,atan()atan2()tan()的逆向操作。它们还有个挺文艺的名字叫“弧函数”,因为在单位圆里,每个角度都对应着圆弧上一段长度。

有个点得先拎清楚,acos()asin()这俩家伙有点“挑食”,它们只能接收-11之间的数字。为啥呢?因为cos()sin()不管塞进去多大的角度,吐出来的结果永远在这个范围里晃悠。所以acos(0)可能对应90°,也可能对应270°,为了不打架,acos()只返回180°之间的角,而asin()只返回-90°90°之间的角。

干活前先掂量掂量

acos()asin()的“挑食”特性,在实际干活时限制挺大。比如直接甩个asin(1.2)过去,CSS立马给你返回个NaN,直接懵圈。这俩更适合那些已经知道比值肯定在合法范围内的场景,比如处理某些特定角度的三角形。

解决渐变不走心的麻烦

在搞渐变的时候,经常碰到个糟心事:想让圆锥渐变(conic-gradient)的背景色块随着盒子大小变化,自动调整起始角度,而不是死死固定住。用linear-gradient时还能用to top right这种关键字糊弄过去,但conic-gradient可没这待遇。

换个思路,把盒子的宽度和高度看作直角三角形的两条直角边。宽是邻边,高是对边,想找到斜边对应的那个角度,atan(高 / 宽)直接安排上。

.element {
  --宽度: 300px;
  --高度: 200px;
  --角: atan(var(--高度) / var(--宽度));
  /* 圆锥渐变默认从顶部开始,得转一下 */
  --旋转: calc(90deg - var(--角));
  background: conic-gradient(from var(--旋转), 
    #84a59d 180deg, #f28482 180deg);
}

这么一搞,不管盒子是宽屏还是瘦高个,渐变那个分割线永远老老实实对着对角。这法子同样能用在双色渐变卡片的制作上,把一个渐变劈成两半,各自从对角往中间跑,拼出来的效果绝绝子。

让眼睛跟着鼠标转

atan2(y, x)这个函数比atan()更懂事儿。atan()处理的是除法,正负号一搅和,角度就容易搞混。比如点(1, 1)(-1, -1),两者比值都是1,atan()根本分不清谁是谁。atan2()就不一样了,它把x和y分开送进去,能精准定位到四个象限的任何位置。

做个会盯着鼠标看的小东西,比如一个卡通角色的眼睛。先得用点JS把鼠标的坐标抓出来,塞到CSS变量里。

const 页面 = document.querySelector("body");
页面.addEventListener("pointermove", (事件) => {
  let 横坐标 = 事件.clientX;
  let 纵坐标 = 事件.clientY;
  页面.style.setProperty("--鼠标-x", `${横坐标}px`);
  页面.style.setProperty("--鼠标-y", `${纵坐标}px`);
});

现在CSS里就有了--鼠标-x--鼠标-y。假设眼睛默认是朝右看的(正x轴方向),把眼睛初始旋转135deg调整个舒服的姿势,然后用atan2()算出需要转的角度。

.眼睛::before,
.眼睛::after {
  rotate: calc(135deg + atan2(var(--鼠标-y), var(--鼠标-x)));
}

这么一写,鼠标挪哪儿,眼睛就瞄哪儿。如果想更精准点,把角色自身的偏移量也减掉,让坐标系原点落在角色身上。

.眼睛::before,
.眼睛::after {
  rotate: calc(
    135deg +
      atan2(
        var(--鼠标-y) - var(--内边距) - var(--角色尺寸) / 2,
        var(--鼠标-x) - var(--内边距) - var(--角色尺寸) / 2
      )
  );
}

把视口宽度变成数字

atan2()tan()这俩组合拳还能干件神奇的事儿——把带单位的长度(比如100vw)转换成纯粹的整数。具体咋整呢?

先定义一个自定义属性--100vw,类型是<length>,初始值0px,不让它继承。然后在根元素上把--100vw设置成100vw。接着就能用数学魔法了:

:root {
  --100vw: 100vw;
  --整数宽度: calc(10000 * tan(atan2(var(--100vw), 10000px)));
}

atan2(100vw, 10000px)算出的是一个角度,这个角度再丢给tan(),就能把单位给消掉,变成纯数字。乘上10000之后,--整数宽度就是视口的整数宽度。这招在需要根据视口尺寸动态调整样式时,简直不要太爽。

那些藏在背后的兄弟

三角函数还有三个“倒过来的兄弟”:正割sec(x)1/cos(x),余割csc(x)1/sin(x),余切cot(x)1/tan(x)。虽然CSS里没直接提供,但用现有的函数一样能算出来。它们跟sincostan一起,都住在那个熟悉的单位圆里,描述着圆上各个点与角度之间的各种关系。