你以为环形布局只能靠 JS 算坐标?今天 CSS 自己会了

写过三年动画,每次想让一个元素绕着圆心转,都要靠 transform: rotate() translateX() rotate() 三层嵌套 hack,调试半天不知道哪个轴歪了——今天 CSScos()sin() 把这件事彻底原生化了,浏览器自己会算坐标。


三角函数进了 CSS,你可能还不知道

Chrome 111、Firefox 118、Safari 15.4 全部稳定,全球覆盖率 91.56%,从 2023 年开始就是 Baseline Widely Available。简单说:所有主流浏览器都支持了,你不需要任何 polyfill,也不需要等任何版本。

cos()sin() 的语法和 calc() 完全一致:

/* cos() 返回 -1 到 1 的数,乘以半径得到 X 坐标 */
transform: translateX(calc(cos(var(--angle)) * var(--radius)));
/* sin() 同理得到 Y 坐标 */
transform: translateY(calc(sin(var(--angle)) * var(--radius)));

CSS 三角函数接受任意角度单位——degradgradturn 都可以,不像 JavaScript 的 Math.cos() 只收弧度,浏览器内部自动换算。


环形布局:三行代码替代一个 JS 库

最典型的场景:把 N 个元素均匀排在圆周上。传统方案是给每个元素单独算坐标或者用 JS 循环注入。现在只需要:

li {
  --angle: calc(360deg / var(--total) * var(--i));
  --r: 8rem;
  position: absolute;
  top: 50%; left: 50%;
  transform:
    translateX(calc(cos(var(--angle)) * var(--r) - 50%))
    translateY(calc(sin(var(--angle)) * var(--r) - 50%));
}

给每个 li 设置不同的 --i,CSS 自动算出它在圆周上的位置,加一个元素只需要在 HTML 里加一行,连一行 CSS 都不用改。


轨道动画:不需要 JS requestAnimationFrame

要做元素绕圆周公转的动画,传统方案是 JS 驱动 Math.cos()/Math.sin() 每帧写入 transform。现在只要注册一个 @property,让浏览器知道 --angle 是个角度值:

@property --orbit-angle {
  syntax: "<angle>";
  inherits: false;
  initial-value: 0deg;
}

@keyframes orbit {
  to { --orbit-angle: 360deg; }
}

.planet {
  --orbit-radius: 100px;
  translate:
    calc(cos(var(--orbit-angle)) * var(--orbit-radius))
    calc(sin(var(--orbit-angle)) * var(--orbit-radius));
  animation: orbit 4s linear infinite;
}

浏览器在合成器线程上运行这个动画,主线程 JS 再忙也不会丢帧。而且这和 JavaScript 方案的核心区别在于:CSS 三角函数在样式计算阶段完成,浏览器可以把整个动画调度到合成层,不需要每帧触发脚本。


波形动画:正弦波完全用 CSS 驱动

如果要在元素沿水平方向移动时,让它同时在垂直方向上做正弦波动,也不需要 JS。只需要把水平位移映射为相位角:

@property --progress {
  syntax: "<number>";
  inherits: false;
  initial-value: 0;
}

@keyframes wave {
  to { --progress: 1; }
}

.shape {
  --PI: 3.1415;
  --WAVE-COUNT: 2;
  --x: calc(var(--progress) * 50vw);
  --alpha: calc(var(--progress) * var(--WAVE-COUNT) * 2 * var(--PI) * 1rad);
  --y: calc(-1 * 3rem * sin(var(--alpha)));

  translate: var(--x) var(--y);
  animation: wave 3s linear infinite;
}

--alpha 把水平位移映射到圆周角度,sin() 算出对应高度。1rad 是关键——把角度值转回弧度供 sin() 使用。


三个坑,踩过才知道

坑一:@property 声明不了就没法平滑过渡。 自定义属性默认是不可插值的,想要 --angle@keyframes 里平滑变化,必须用 @property 注册类型。不注册的话动画会跳帧——整个动画变成一帧一帧更新,不是平滑过渡。

坑二:cos()sin() 返回的是没有单位的数。 乘以半径得到坐标值,这是对的;但如果忘了乘以长度单位,直接写 translateX(cos(var(--angle))) 不会报错,但浏览器会忽略这个值。始终用 calc(cos(...) * var(--radius)) 明确长度。

坑三:Safari 在某些老版本里 sin(0.25turn) 行为不一致。 Safari 15.4 开始支持,但早期版本的 turn 单位换算有小数精度问题。如果你的受众有 Safari 15.x 用户,建议统一用 deg


三步下一步

  1. 现在就能用: 查一下你的 CSS 库里有没有 rotate() translateX() rotate() 这种三层嵌套 transform,有的话就可以用 cos()/sin() 替换,先从环形菜单开始改。
  2. 加上 @property 动画: 把你想平滑变化的角度注册为 @property,然后用 @keyframes 驱动,替代 JS requestAnimationFrame
  3. @supports 降级: 如果受众里有 Safari 15 以下的用户(国内目前极少),用 @supports (top: calc(sin(1turn) * 1px)) 包裹三角函数逻辑,给老浏览器一个静态位置。

三角函数不是”会用 JS 的人才需要”的技能,它是 CSS 布局工具箱里又一件被浏览器原生化的能力。从环形菜单到轨道动画,从波形图到仪表盘指针,你不再需要为了算坐标接一个 30KB 的数学库了。

评论区

0 条评论

登录后可评论。

阿柯·前端架构 12 阅读