现代 CSS 解决方案:CSS 原生支持的三角函数
Category(分类): CSS Status: 已整理
CSS 中有许多数学函数,可以通过计算生成长度、比例、颜色和动画参数。常见函数包括:
calc():对长度、百分比、数字等 CSS 值进行计算;min():从多个值中取较小值;max():从多个值中取较大值;clamp():将值限制在最小值和最大值之间;sin()、cos()、tan():根据角度计算正弦、余弦和正切值。
本文重点介绍 CSS 原生三角函数,以及它们在轨迹动画、圆弧、Loading、波浪线和 CSS 绘图中的应用。
一、CSS 三角函数的基本语法
sin()、cos() 和 tan() 属于 CSS Values and Units Level 4 中的数学函数。它们接收一个能够解析为角度或无单位数字的值,并返回无单位数字:
.box {
width: calc(sin(30deg) * 100px);
height: calc(cos(45deg) * 100px);
}
三角函数返回的结果具有以下特点:
sin()和cos()的结果范围通常是-1到1;tan()在接近90deg、270deg等位置时可能变得非常大;- 无单位数字会按弧度解释;
- 带角度单位的值可以直接使用。
常见角度单位:
.angle {
--a: 30deg;
--b: 0.5rad;
--c: 100grad;
--d: 0.25turn;
}
deg() 和 rad() 不是这里需要使用的通用角度转换函数。通常直接使用 deg、rad、grad 或 turn 单位即可。
不要把 tan() 直接用于 opacity
例如:
/* 不推荐:tan(60deg) 约为 1.732,超出常用 opacity 范围 */
.bad-example {
opacity: tan(60deg);
}
如果需要将正弦结果映射到 0 到 1,可以这样写:
.good-example {
opacity: calc((sin(30deg) + 1) / 2);
}
不过,透明度一般不需要使用三角函数,示例中更值得关注的是数值映射方式。
浏览器支持与渐进增强
现代 Chrome、Edge、Firefox 和 Safari 已经支持 CSS 三角函数,但较旧浏览器可能不支持。可以使用 @supports 做特性检测:
@supports (width: calc(1px * sin(45deg))) {
.trig-effect {
--supports-trig: 1;
}
}
@property 属于可注册自定义属性相关能力,浏览器支持情况还需要单独确认。需要兼容旧浏览器时,应准备静态回退、JavaScript 计算或预编译后的 CSS。
二、三角函数与运动轨迹
三角函数最直观的用途之一是生成周期性运动。正弦和余弦曲线的取值会随着角度变化而变化:
sin()适合生成上下摆动的值;cos()与sin()相差一个相位;- 同时使用
sin()和cos()可以生成圆周轨迹。

1. 基础小球
<div class="g-single" aria-hidden="true"></div>
.g-single {
width: 20px;
height: 20px;
border-radius: 50%;
background: #000;
}

2. 使用 @property 实现正弦轨迹
未注册的 CSS 自定义属性通常不能平滑插值。为了让角度和长度在关键帧之间连续变化,需要使用 @property 注册它们:
@property --distance {
syntax: '<length>';
inherits: false;
initial-value: 0px;
}
@property --angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
然后让小球沿水平方向移动,同时根据角度计算垂直偏移:
.g-single {
position: relative;
width: 20px;
height: 20px;
border-radius: 50%;
background: #000;
animation: move-sine 5s infinite ease-in-out;
transform: translate(
calc(var(--distance) - 40vw),
calc(5 * sin(var(--angle)) * 1em)
);
}
@keyframes move-sine {
from {
--distance: 0px;
--angle: 0deg;
}
to {
--distance: 80vw;
--angle: 1080deg;
}
}
这里:
--distance从0px变化到80vw,控制水平方向;--angle从0deg变化到1080deg,表示三个周期;5 * sin(var(--angle)) * 1em控制竖直方向的振幅;transform只改变视觉位置,通常不会直接触发布局。

3. 多个小球组成曲线
如果让多个小球沿同一轨迹运动,并为每个小球设置不同的动画延迟,就可以形成连续的波形效果。
HTML 可以由模板或 JavaScript 生成。示意结构如下:
<ul class="g-multi" aria-hidden="true">
<li></li>
<li></li>
<!-- 实际项目中可以生成更多 li,例如 80 个 -->
</ul>
下面的重复代码使用了 Sass,因此代码块应标记为 scss,不能直接当作原生 CSS 使用:
$count: 80;
.g-multi {
position: relative;
width: 100%;
height: 8em;
margin: 0;
padding: 0;
list-style: none;
}
.g-multi li {
position: absolute;
top: 50%;
left: 0;
width: 20px;
height: 20px;
margin-top: -10px;
border-radius: 50%;
background: #000;
animation: move-sine 5s infinite ease-in-out;
transform: translate(
calc(var(--distance) - 40vw),
calc(5 * sin(var(--angle)) * 1em)
);
}
@for $i from 1 through $count {
.g-multi li:nth-child(#{$i}) {
animation-delay: #{($i - 1) * -0.06}s;
}
}
@keyframes move-sine {
from {
--distance: 0px;
--angle: 0deg;
}
to {
--distance: 80vw;
--angle: 1080deg;
}
}
如果要完全使用原生 CSS,可以在构建阶段由 JavaScript、模板循环或手写规则生成不同的 animation-delay,而不能直接使用 Sass 的 @for 和 $count。

三、快速实现圆弧和圆周轨迹
在没有三角函数之前,CSS 实现圆周运动通常需要嵌套元素、旋转父元素、负外边距或多个关键帧。使用 sin() 和 cos() 后,可以直接根据角度计算横纵坐标。

1. 小球沿圆周运动
<div class="orbit-dot" aria-hidden="true"></div>
@property --orbit-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
.orbit-dot {
position: absolute;
top: 50%;
left: 50%;
width: 20px;
height: 20px;
margin: -10px;
border-radius: 50%;
background: #000;
animation: orbit 3s infinite linear;
transform: translate(
calc(sin(var(--orbit-angle)) * 10vmin),
calc(cos(var(--orbit-angle)) * 10vmin)
);
}
@keyframes orbit {
from {
--orbit-angle: 0deg;
}
to {
--orbit-angle: 360deg;
}
}
sin() 和 cos() 的结果都是无单位数字,乘以 10vmin 后就得到长度:
x = sin(angle) × radius
y = cos(angle) × radius
这里从 0deg 到 360deg 会完成一周运动。使用 cos() 作为纵坐标时,小球初始位置会位于圆周上方;如果想改变起点,可以调整角度或交换两个函数。

2. 使用三角函数制作 Loading
多个小球设置不同的动画延迟,就可以组成旋转 Loading。需要给容器明确的尺寸和定位上下文:
<ul class="loader" role="status" aria-label="加载中">
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
$count: 10;
@property --loader-angle {
syntax: '<angle>';
inherits: false;
initial-value: 0deg;
}
.loader {
position: relative;
width: 160px;
height: 160px;
margin: 0 auto;
padding: 0;
list-style: none;
}
.loader li {
position: absolute;
top: 50%;
left: 50%;
width: 8px;
height: 8px;
margin: -4px;
border-radius: 50%;
animation: loader-orbit 3s infinite linear;
transform: translate(
calc(sin(var(--loader-angle)) * 60px),
calc(cos(var(--loader-angle)) * 60px)
);
}
@for $i from 1 through $count {
.loader li:nth-child(#{$i}) {
animation-delay: #{($i - 1) * -0.15}s;
background: hsl(#{100 + $i * 15} 80% 60%);
}
}
@keyframes loader-orbit {
from {
--loader-angle: 0deg;
}
to {
--loader-angle: 360deg;
}
}
这里的 @for 是 Sass 代码,负责生成不同的延迟和颜色。浏览器最终接收的是编译后的普通 CSS 与 @property、三角函数。

四、使用三角函数生成波浪线
box-shadow 支持多个阴影。通过 Sass 循环生成一组不同位置的阴影,再用正弦函数计算横向偏移,就可以近似绘制波浪线。
这种方案的核心思路是:
- 沿纵向逐步增加阴影位置;
- 使用
sin()计算每个阴影的横向偏移; - 将所有阴影合并到同一个元素上;
- 通过多个元素和不同颜色生成多条曲线。
Sass 示例
@function shadow-set($step, $amplitude, $color) {
$shadow: 0 0 0 0 $color;
@for $i from 1 through 50 {
$angle: $i * 15deg;
$x: calc(sin(#{$angle}) * #{$amplitude});
$y: $i * $step;
$shadow: $shadow, #{$x} #{$y} 0 0 $color;
}
@return $shadow;
}
.wave {
width: 10px;
height: 10px;
margin: 0 auto;
border-radius: 50%;
background: #f00;
box-shadow: shadow-set(3px, 3px, #f00);
}
.wave:nth-child(2) {
width: 6px;
height: 6px;
background: #fc0;
box-shadow: shadow-set(3px, 3px, #fc0);
}
.wave:nth-child(3) {
width: 4px;
height: 4px;
background: #000;
box-shadow: shadow-set(2px, 3px, #000);
}
<div class="wave"></div>
<div class="wave"></div>
<div class="wave"></div>
编译后,一个元素可能包含几十个甚至更多阴影:


这种方案的局限
- 编译后的 CSS 体积较大;
- 阴影数量越多,绘制成本越高;
- 曲线精度和阴影尺寸之间需要权衡;
- 不适合复杂、动态或大面积图形;
- SVG、Canvas 或渐变可能更适合生产环境。
因此,这种技巧适合作为 CSS 实验、装饰性图案或小面积效果,不应默认作为所有波浪线的最佳方案。
五、用曲线进行 CSS 创意绘图
有了根据角度计算位置的能力后,可以把多个元素、阴影、渐变、圆角和裁剪结合起来,制作书签、鱼、海草和其他装饰性图案。

示例参考:
- CSS Cos/Sin Math And box-shadow
- CSS Cos/Sin Math And box-shadow - bookmark
- CSS-doodle 作者的 CodePen 作品
- CSS-doodle border-radius 示例
- CSS-Doodle fish & seaweed


这些案例更适合用来理解数学函数和 CSS 绘图的结合。生产项目中还需要关注:
- 动画是否影响低端设备;
- 图形是否需要响应式缩放;
- 是否需要支持减少动态效果的用户偏好;
- 是否会造成大量阴影或图层;
- 重要内容是否应该使用 SVG 或真实 HTML 结构表达。
六、动画和可访问性注意事项
如果动画不是内容传达的必要部分,应尊重用户的减少动态效果偏好:
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
装饰性小球、波浪线和 Loading 图形可以使用 aria-hidden="true"。如果 Loading 表示真实的加载状态,应提供可访问名称,例如:
<div class="loader" role="status" aria-label="正在加载">
<span aria-hidden="true"></span>
</div>
不要仅使用复杂 CSS 图形表达重要信息,必要时应提供文本或其他语义化内容。
七、兼容性和降级建议
CSS 三角函数、@property 和复杂的 CSS 数学表达式应根据目标浏览器测试。可以准备以下降级方式:
- 不支持三角函数时显示静态图形;
- 使用 JavaScript 计算坐标;
- 在构建阶段由 Sass 或脚本预计算数值;
- 使用 SVG、Canvas 或图片作为回退;
- 对装饰性动画关闭效果,而不是影响页面主要内容。
例如:
.orbit-dot {
/* 不支持三角函数时的静态回退位置 */
transform: translate(0, -10vmin);
}
@supports (transform: translate(calc(sin(45deg) * 1px))) {
.orbit-dot {
transform: translate(
calc(sin(var(--orbit-angle)) * 10vmin),
calc(cos(var(--orbit-angle)) * 10vmin)
);
}
}
总结
sin()、cos()和tan()是现代 CSS 数学函数,不是 CSS3 的传统基础属性;- 三角函数可以直接使用
deg、rad、grad和turn等角度单位; - 无单位数字按弧度解释,不需要使用
deg()或rad()转换; sin()和cos()适合生成周期运动,二者结合可以生成圆周轨迹;- 让 CSS 自定义属性平滑动画时,通常需要通过
@property注册类型; - Sass 的
@for、@function和变量插值不能直接交给浏览器执行; box-shadow生成曲线很有趣,但编译后代码量和绘制成本可能较高;- SVG、Canvas、渐变和
clip-path可能更适合生产环境中的复杂图形; - 使用动画时应考虑浏览器兼容性、性能、降级和
prefers-reduced-motion; - CSS 数学函数增加了 CSS 的表现力,但也应根据场景控制复杂度。