很多人第一眼看到那种流动的、仿佛自带生命力的网页背景时,脑子里蹦出来的多半是 WebGL、Canvas、Three.js 这些重型武器。但实际上,我按下 F12 翻了半天,最后在样式表里看到的可能只是一段不到 80 行的 CSS——几个渐变叠在一起,打了层模糊,混了个混合模式,加了个旋转动画,整张画面就"活"了。这种用纯 CSS 代码生成视觉作品的做法,正是 CSS 生成艺术。它适合前端工程师、视觉设计师、创意开发爱好者去玩,不需要引入任何绘图库,打开一个 html 文件就能跑。这篇文章我会从原理讲到实战,再把我踩过的坑一次性倒出来。
1. 为什么说 CSS 是被低估的生成艺术媒介
1.1 浏览器替我们干完了所有渲染的脏活
很多人一听"用代码画画",条件反射就是学习 Canvas API 或 WebGL,觉得那才是"正规军"。CSS 在他们眼里只是排版工具:改颜色、调间距、排排 div。这个认知误区让我觉得挺可惜的。
你仔细想想,CSS 背后其实是一套完整的渲染管线。渐变、滤镜、裁剪、遮罩、混合模式、GPU 合成、动画调度,这些东西浏览器全都内置了。你在 CSS 里写一行 background: conic-gradient(...),浏览器底层会帮你把角度、色相、插值全部计算好;你写一个 filter: blur(20px),浏览器直接调用 GPU 去做高斯模糊。这些活儿如果放到 Canvas 里实现,你得自己写着色器、自己管理缓冲区、自己做性能优化。而 CSS 把这些脏活累活全部封装起来了,你只需要告诉它"我要什么",不用操心"怎么算"。
这就是为什么我觉得 CSS 是一个被严重低估的创作媒介:它的抽象层次恰到好处。你不需要理解像素怎么填充,不需要理解矩阵变换的数学细节,但你能精准地控制规则、节奏和参数,然后让浏览器替你完成最繁琐的渲染计算。
1.2 声明式语言恰好是"参数化创作"的天然载体
生成艺术的核心从来不是"手绘每一帧",而是"定义一套规则,让规则自己生长出画面"。你写一个圆,它是死的;但你写一个"半径由变量控制的圆,变量随时间变化",它就成了活的——这就是参数化。
CSS 的声明式模型和生成艺术简直是天作之合。你用自定义属性(CSS Variables)定义一组参数,用 calc()、clamp()、三角函数把这些参数组合成几何形状,再用 animation 驱动参数变化,整个画面就会按照你设定的规则自动演化。而且这种演化不是临时的,你改一个参数,所有引用它的地方会同步更新。
我还记得第一次体会到这种感觉的场景:我给自己博客写了一个动态背景,定义了五六个 CSS 变量控制色相、半径、速度。半夜睡不着改了一个色相数值,整个页面的氛围完全变了,像换了一幅画。那一刻我意识到,CSS 生成艺术不是"写死一张图",而是"造了一个会自己画画的引擎"。你输进去的是规则和约束,它吐出来的是无数种可能。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 撑起 CSS 生成艺术的五块基础积木
既然要玩 CSS 生成艺术,就得先把手上的工具摸清楚。我平时用得最多的有五类:渐变、裁剪与遮罩、混合模式与滤镜、CSS 变量、伪元素与选择器。下面挨个说。
2.1 渐变:一张图都不用的纹理引擎
渐变是整个 CSS 生成艺术里最不值钱也最好用的工具。linear-gradient 负责线性过渡,radial-gradient 负责从中心向外扩散,conic-gradient 负责从圆心开始绕圈旋转。你把这三种渐变当成颜料,把 background-size 当成画笔的粗细,就能在一张空白画布上调制出无穷无尽的纹理。
最简单的例子:一条条纹背景只需要两行 repeating-linear-gradient 就可以实现。把渐变角度改成 45 度,再把 background-size 调小,它就会像瓷砖一样铺满整个屏幕。更进阶的玩法是同时叠十几层 radial-gradient,每一层用不同的 position 和 size,就能做出类似星云、血管、树根那种自然生长的形态。
我还特别喜欢 conic-gradient。它天生适合做扇形、雷达图、齿轮、日晷这类绕中心旋转的图形。配合 filter: hue-rotate() 动画,一张静态的扇形图可以变成不断换色的"流光转盘"。很多看起来复杂得不得了的生成艺术,底层其实就是几个渐变在互相对齐、错位、叠加。
2.2 clip-path 与 mask:在 CSS 里做减法雕刻
如果你把渐变理解成"加法"——往画布上涂颜料,那 clip-path 和 mask 就是"减法"——从已有形状上裁掉一部分。
clip-path 可以直接把元素裁剪成圆形、多边形甚至任意路径。我经常用它配合渐变背景做几何切割:一个正方形背景,裁掉左上角,就得到一个斜切平面;裁成六边形,错位排列后就是蜂巢纹理。它对生成艺术最大的价值在于:你不需要真的准备一张 PNG 素材,形状完全由参数控制,改一个顶点坐标,整个图案就变了。
mask 和 clip-path 的差别在于:clip-path 是硬裁剪,边缘非黑即白;mask 是根据透明度/亮度做渐变遮罩。一个元素如果挂了 mask: radial-gradient(circle, white 0%, transparent 70%),它的边缘就会自然淡出,看起来像一个柔光斑。这个行为和滤镜 blur 的模糊感完全不同:blur 是把边缘"抹开",mask 是让边缘"透明掉"。在生成艺术里,柔和的边缘通常更讨喜,所以我做光晕、雾、粒子效果时,mask 是比 blur 更可控的选择。
2.3 混合模式与滤镜:渲染管线的"后期调色"
前面说的都是单层图形的制作,但真正的生成艺术很少只有单层。把多层图形叠在一起,它们之间会发生什么?这就是混合模式(mix-blend-mode)的舞台。
你可以把混合模式理解成 Photoshop 里的图层模式:screen 会让重叠区域变亮,适合做光效;multiply 会变暗,适合做叠加阴影;overlay 则同时保留亮部和暗部的对比,像给照片加了对比度滤镜。在 CSS 里用 mix-blend-mode,多个半透明渐变图层叠在一起,会互相影响,产生单层永远调不出来的复杂色彩。
滤镜方面我常用的三件套是 blur()、hue-rotate() 和 contrast()。blur() 负责把锋利的边缘变柔和,几乎是"流体感"的标配;hue-rotate() 负责整体色相偏移,加在动画里可以循环变色;contrast() 则是"压缩魔法"——如果你把一张模糊的图和大对比度滤镜结合,再用 mix-blend-mode 叠加,某些滤镜算法会产生类似液态金属的扭曲边缘,这个技巧在 CSS 社区里被称为"gooey effect",原理就是 blur 让边缘融合、contrast 又把融合后的边缘重新收紧。
2.4 CSS 变量与 calc():让画面第一次可以"调参"
我前面已经强调了参数化的重要性,而 CSS 变量就是实现参数化的核心工具。你可以把变量理解为整个作品的控制面板:定义在 :root 或容器上,任何子元素都用 var(--xxx) 引用。
css复制:root {
--hue-a: 180;
--hue-b: 320;
--rotation: 30deg;
--blur: 20px;
}
.art {
background: linear-gradient(
var(--rotation),
hsl(var(--hue-a) 80% 60%),
hsl(var(--hue-b) 80% 60%)
);
filter: blur(var(--blur));
}
用 calc() 可以让参数之间产生联动。比如定义 --count: 6,然后用 calc(100% / var(--count)) 计算每个单元的尺寸,当你把 --count 从 6 改成 12,整个拓扑结构自动改变。这种"一个参数驱动全局"的能力,是生成艺术最迷人的地方,也是我把 CSS 变量放在所有技术点核心位置的原因。
2.5 伪元素与选择器:用极少的 DOM 换极大的画面密度
最后一块积木是 DOM 结构与选择器。如果你观察过经典 CSS 生成艺术作品的源码,会发现它们往往只用了一个 <div>,剩下的全靠 ::before 和 ::after 撑场面。两个伪元素相当于每个标签免费多送两层画布,三层叠加就能做出很多层次了。
选择器也是生成规则的一部分。:nth-child(3n + 1) 可以筛选出第 1、4、7 个元素做特殊处理,这种"按规律改变元素状态"的思路,本质上就是在用规则生成视觉节奏。相邻兄弟选择器(A + B)、通用兄弟选择器(A ~ B)则可以表达"当我变化时,我的兄弟也要变化"的联动关系——这有点像一个元素的行为"涌现"到整个系统里,是生成艺术里非常高级的玩法。
| 基础积木 | 核心作用 | 常用场景 |
|---|---|---|
| 渐变 | 绘制底纹、光晕、流色 | 背景、粒子、色块 |
| 裁剪与遮罩 | 改变形状、控制透明度 | 几何切割、柔光、蒙版 |
| 混合模式与滤镜 | 图层叠加、后期处理 | 光效、流体、故障感 |
| CSS 变量与 calc | 参数化、联动计算 | 变量驱动、响应式 |
| 伪元素与选择器 | 提升视觉密度、规则涌现 | 低 DOM 高复杂度 |
3. 案例实战一:参数化涟漪,从"圆环"到"会呼吸的画面"
3.1 目标与 HTML 结构
我们先做一个经典效果:水波涟漪光圈扩散。这是 GitHub 404 页面和很多品牌 Landing Page 喜欢用的背景元素。我的目标很简单:页面中央有几圈半透明的光圈,从中心向外扩散,到达最大半径后消失,循环往复。而且圈数、速度、间隔、颜色全部可以用参数控制。
HTML 结构上我用了容器加 5 个 <i>:
html复制<div class="ripple">
<i style="--i:0"></i>
<i style="--i:1"></i>
<i style="--i:2"></i>
<i style="--i:3"></i>
<i style="--i:4"></i>
</div>
你可能注意到了,每个 <i> 上我只设了一个 --i,用来标记它是第几个。这比写 5 个 class 干净得多,后面你能看到这个 --i 是如何被计算成动画延迟的。
3.2 用 radial-gradient 画出"光圈"
每个 <i> 的视觉效果我用 radial-gradient 实现:透明背景上画一个半透明圆环。圆环的样子是这样的——38% 以下全透明,38% 到 42% 之间是半透明颜色,超过 48% 又回到全透明。你可以把它理解成一个甜甜圈:中心有洞,边缘有透明的渐晕。
css复制.ripple i {
position: absolute;
inset: 0;
border-radius: 50%;
background: radial-gradient(
circle,
transparent 38%,
var(--ring) 42%,
transparent 48%
);
}
这里的 --ring 是圆环的颜色,我会在容器上定义。用 var(--ring) 而不是写死颜色,是为了后面调起来方便。
3.3 用 CSS 变量接管所有可调参数
容器的参数定义如下:
css复制.ripple {
--ring: rgba(120, 200, 255, 0.45);
--size: 260px;
--duration: 2.4s;
--interval: 0.45s;
position: relative;
width: var(--size);
height: var(--size);
}
--ring:圆环颜色和透明度--size:整个涟漪的尺寸--duration:一圈涟漪扩散的完整时长--interval:相邻两圈涟漪的启动时间差
接下里是关键一步:让每个 <i> 的动画延迟由它的序号 --i 和 --interval 计算出来。CSS 变量是可以用 calc() 运算的:
css复制.ripple i {
animation: ripple-spread var(--duration) cubic-bezier(0.2, 0.6, 0.35, 1) infinite;
animation-delay: calc(var(--i) * var(--interval));
}
--interval 乘上 --i,第 0 个圆环立即启动,第 1 个圆环晚 0.45s,第 2 个再晚 0.9s……这样 5 个圆环就会错峰扩散,形成水波那种"一环接一环"的节奏感。如果你把 --interval 调成 0,所有圆环会同时扩散;调到 1s,节奏会变得非常舒缓。这种"一个参数改变整体节奏"的体验,就是 CSS 生成艺术的乐趣所在。
3.4 animation 让涟漪真正"散开"
动画本身用 scale 和 opacity 实现。起始状态是缩小到 10% 大小、透明度接近 0.9,结束状态是放大到 100% 大小、完全透明:
css复制@keyframes ripple-spread {
0% {
transform: scale(0.1);
opacity: 0.9;
}
100% {
transform: scale(1);
opacity: 0;
}
}
注意这里我只动了 transform 和 opacity,没有去动 left、top 或 background-size。原因后面性能章节会详细说,总结起来就是:transform 和 opacity 走浏览器的合成器线程,GPU 直接处理,不会触发布局和重绘,跑多少圈都不卡。
最后加一个 hover 交互,让你直观感受参数的作用——鼠标悬停时涟漪会加速,移开后恢复慢速:
css复制.ripple:hover {
--duration: 1.1s;
--interval: 0.2s;
}
这个 hover 的效果是:你把鼠标放上去,"波纹频率"会变快,像一个被触碰的水面。虽然只是改了两个变量,但整个动态完全变了,非常神奇。
4. 案例实战二:极光流体,混合模式让色层"自己画画"
4.1 conic-gradient 与 blur:先造出柔和的色层
静态波纹做完了,我们来做一个能当全屏背景的动态极光。这类效果在高端 SaaS 官网和创作者个人主页上经常见,第一眼看过去你绝对会以为那是视频素材,但实现起来就几行 CSS。
核心思路是:用 conic-gradient 制作一圈一圈的彩色色带,再用 blur() 把它们柔化。原理很简单:极光本质上就是几条不同颜色的光带在空间里扭动,conic-gradient 的色带从圆心向四周发散,天然就是"扭动"的形状;blur() 把色带的边缘晕开,同时让几层色带之间的颜色互相渗透,产生柔和过渡。
css复制.aurora-layer.one {
background: conic-gradient(
from 0deg,
#ff6ec4,
#7873f5,
#4adede,
#f7d060,
#ff6ec4
);
filter: blur(40px);
}
from 0deg 表示色带从 0 度开始排布。你用 from 90deg、from 180deg 就能旋转整个色带。有时候我会故意给两层设置不同的起始角度,这样叠加之后色彩错位,更接近真实极光的复杂感。
4.2 旋转动画与混合模式:让色层"动起来"并互相融合
光有静态色层不够,极光要流动起来才好看。最简单的做法是让每一层都旋转,但旋转整个元素会露白——大圆转起来,原来覆盖的角落会空出来。解决办法是把色层放大,让尺寸明显超过容器,再通过 translate(-50%, -50%) 居中。
css复制.aurora-layer {
position: absolute;
top: 50%;
left: 50%;
width: 150%;
height: 150%;
transform: translate(-50%, -50%);
}
@keyframes spin {
from { transform: translate(-50%, -50%) rotate(0deg); }
to { transform: translate(-50%, -50%) rotate(360deg); }
}
注意:transform 里包含了 translate(-50%, -50%) 和 rotate() 两部分。如果你只写 rotate() 而不带 translate,旋转中心会是元素的左上角,整个图层会翻出屏幕。这个错误我早期踩过好几次,每次都是"动画转起来了但位置乱飞"。
然后加入 mix-blend-mode: screen。screen 混合模式的特点是:重叠区域的亮度等于两者的亮度叠加,但黑色完全不参与。这意味着几层彩色渐变叠加后,相交部分会变得更亮、更鲜艳,像真的光在空气中混合一样。这个效果单独看每一层都很普通,但叠加起来之后,画面自己就产生了复杂的光斑和流动感。
4.3 用 @property 实现真正的角度插值
如果要让极光的"色相"本身发生旋转,而不是旋转整个元素,一个优雅的做法是注册一个自定义属性 --angle,然后让动画驱动 --angle 从 0deg 运动到 360deg。
css复制@property --angle {
syntax: '<angle>';
initial-value: 0deg;
inherits: false;
}
.aurora-layer.one {
background: conic-gradient(from var(--angle), #ff6ec4, #7873f5, #4adede, #f7d060, #ff6ec4);
animation: angle-spin 8s linear infinite;
}
@keyframes angle-spin {
to { --angle: 360deg; }
}
为什么需要 @property?因为普通的 CSS 变量在 animation 里改变时,浏览器不知道它是一个角度类型,无法做插值运算。你从 0deg 改到 360deg,它会在某一帧直接"跳"过去。而注册了 @property --angle 之后,浏览器知道这是角度类型,就能在两个关键帧之间平滑插值。这是 CSS 比较新的能力,Chrome 85+ 和 Safari 16.4+ 支持得很好,是目前做"参数动画"最优雅的写法。
4.4 完整结构与性能提示
极光效果我通常做三层:第一层颜色最鲜艳,第二层偏冷色、模糊更大,第三层颜色很淡、负责整体提亮。三层旋转速度不一致,方向也不一致,这样交错的动态才不会让人看腻。
html复制<div class="aurora-bg">
<div class="aurora-layer one"></div>
<div class="aurora-layer two"></div>
<div class="aurora-layer three"></div>
</div>
性能上有一点要特别提醒:filter: blur(40px) 和 mix-blend-mode: screen 都是比较重的操作,如果嵌套层数太多,移动端很容易掉帧。我自己的经验是三层左右是安全线,再往上加就要留意整体面积和模糊半径了。另外,在图层上加上 will-change: transform 会帮助浏览器提前优化,但也别滥用,每个图层都加反而会占用额外 GPU 内存。
5. 案例实战三:交互式生成,让用户参与创作
5.1 JS 只写数据,CSS 负责渲染
前面两个案例都是纯 CSS 动画,但它们有一个共同点:用户只能看,不能动。做生成艺术还有一个特别吸引人的方向——让用户的鼠标、滚动、触摸行为直接变成画面参数。
这个设计的核心哲学是:JS 只负责写 CSS 变量,CSS 负责根据变量渲染一切。你可以在 mousemove 事件里更新元素的 --mx、--my 变量,然后 CSS 里的 radial-gradient 位置就会跟着鼠标移动。
css复制body {
--mx: 50%;
--my: 50%;
background:
radial-gradient(
circle at var(--mx) var(--my),
rgba(255, 255, 255, 0.12),
transparent 40%
),
linear-gradient(135deg, #1b1035, #0d1b2a, #1b1035);
}
js复制const body = document.body;
document.addEventListener('mousemove', (e) => {
const x = (e.clientX / window.innerWidth) * 100;
const y = (e.clientY / window.innerHeight) * 100;
body.style.setProperty('--mx', `${x}%`);
body.style.setProperty('--my', `${y}%`);
});
这样做的好处是:即使是每秒触发几十次的 mousemove,浏览器也只需要更新元素的自定义属性,不会触发任何 HTML 解析或者样式重算。CSS 的渐变位置变化会由渲染进程按帧率去合成分层,性能和流畅度都远好过用 JS 直接改 style 的多个属性。
5.2 把鼠标速度变成"能量值",驱动画面色相
光有个光斑跟随鼠标,还没那么"生成艺术"。我们可以把鼠标移动的速度计算出来,转成一个数值,喂给 CSS 变量,驱动画面色相偏移。鼠标移动越快,画面色彩越偏暖;鼠标停住,色彩回归冷色。
js复制let lastX = 0, lastY = 0;
let speed = 0;
document.addEventListener('mousemove', (e) => {
const dx = e.clientX - lastX;
const dy = e.clientY - lastY;
speed = Math.min(100, Math.abs(dx) + Math.abs(dy));
lastX = e.clientX;
lastY = e.clientY;
body.style.setProperty('--hue-offset', `${speed * 2}deg`);
});
CSS 那边维护一个默认的色相偏移,然后把它加到渐变或者 hue-rotate() 滤镜里。这样整个画面的色调就和用户的动作产生了直接关联。我在实际项目中做过一个类似效果,用户反馈说"像在用手电筒到处照"——那种控制感是纯 CSS 动画给不了的。
5.3 hover 延迟关闭:让光斑拥有"余晖"
还有一个很有氛围感的细节:鼠标移出元素后,交互光斑不要立刻消失,而是像余晖一样慢慢淡出。实现这个效果的技巧是给 transition-delay 设置两个不同的值:默认情况下延迟 0.6 秒关闭,hover 时延迟 0 秒立刻出现。
css复制.light {
--mx: 50%;
--my: 50%;
opacity: 0;
transition: opacity 0.5s ease 0.6s; /* 默认延迟0.6s淡出 */
}
.container:hover .light {
opacity: 1;
transition-delay: 0s; /* hover时立即出现 */
}
这里的关键是默认态的 transition-delay 写在 transition 简写里,hover 时用一个更具体的 transition-delay: 0s 覆盖。效果就是鼠标一放上去光斑马上亮起,移开后等 0.6 秒才开始消失。这个延迟让画面多了一层"黏性",整体高级感会提升不少。
5.4 用 prefers-reduced-motion 尊重用户偏好
交互属于锦上添花,但别忘记后台可能坐着一位对动画敏感的用户。系统设置里开启"减少动态效果"的人,看到满屏旋转的极光可能会头晕。专业做法是加一个媒体查询:
css复制@media (prefers-reduced-motion: reduce) {
.aurora-layer,
.ripple i {
animation: none;
}
}
在这个模式下,页面保留静态画面,但取消所有循环动画。这个细节虽然不影响代码运行,但影响体验口碑。
6. 性能、兼容性与踩坑实录
6.1 合成器只认 transform 和 opacity
做 CSS 生成艺术最容易被忽视的问题是性能。很多人写动画时喜欢用 top、left、width、background-position,但这些属性一旦变化,浏览器要重新计算布局(Layout)再重新绘制(Paint),整个过程都在 CPU 上跑,随便几十个元素同时动起来,页面就会发烫掉帧。
正确做法是:凡是需要动的属性,尽量只使用 transform 和 opacity。transform 控制位置、缩放、旋转,opacity 控制透明度,这两个属性走的是合成器线程(Compositor),直接把计算好的图层丢给 GPU 合并,不涉及布局和绘制。上面三个案例里,波纹用的是 scale + opacity,极光用的是 rotate,交互光斑用的是透明度过渡,都是这个原则的体现。
6.2 mix-blend-mode 失效的层叠上下文陷阱
mix-blend-mode 是我用过最需要警惕的属性。它本身效果强大,但有严格的边界条件。最常见的问题是:mix-blend-mode 写在子元素上,结果父元素只要加了一个 filter、opacity、transform 或者 isolation,混合模式的作用范围就会改变。
举个例子:我曾在极光图层外面嵌套了一个带 filter: blur() 的容器,结果里面的 mix-blend-mode: screen 完全失效,颜色不再叠加,画面像被"拍平"了。原因是父元素的 filter 会创建一个新的层叠上下文,导致子元素的混合模式只能与父元素内部的背景混合,而不是与整个页面背景混合。
解决思路有两个:要么调整 DOM 结构,把滤镜和混合模式放在同一层;要么在需要混合的元素上显式写 isolation: isolate,划清楚混合边界。遇到"混合模式明明写了却没效果"的诡异情况,先查层叠上下文,问题基本就迎刃而解。
6.3 @property 的兼容边界与优雅降级
@property 是近年 CSS 最有用的新特性之一,但它的兼容性确实有阶梯差:Chrome 85+ 和 Edge 都早支持了,Safari 到 16.4 才终于支持,Firefox 更是到 128 才开始支持。如果你要面向存量用户,最好做一个降级方案:在不支持 @property 的浏览器里,让动画退化为"跳变"而不是完全卡死。
一个朴素的做法是先用普通 CSS 变量实现基础效果,再把 @property 的注册当成"渐进增强"。当浏览器不支持时,--angle 的动画无法插值,画面会在 0deg 和 360deg 之间快速跳变,虽然不如平滑流动好看,但至少不会白屏。如果你想做得更精细,可以用 @supports 做能力检测:
css复制@supports (background: conic-gradient(from var(--angle), red, blue)) {
.aurora-layer.one {
animation: angle-spin 8s linear infinite;
}
}
6.4 高 DPI 屏幕与视口适配
在高分屏(2x、3x 的视网膜屏)上,CSS 的 1px 会被映射成多个物理像素。如果你的生成艺术用了很多发丝级别的细线或小光点,在普通屏上看着正好,到高分屏上可能会显得过于纤细甚至闪烁。解决办法是适当调大最小尺寸,或者用 clamp() 限定尺寸区间,让它在不同屏幕上都有合适的表现。
还有一个适配细节:全屏背景最好用百分比、视口单位(vw/vh)和 clamp() 组合,而不是写死像素。比如极光的图层尺寸我会写成 width: clamp(600px, 150%, 1200px),避免在超宽屏上拉伸变形。
