很多搞前端的兄弟都会遇到一个灵魂拷问:CSS除了写页面,还能干什么?我自己的答案很明确——它能用来生成艺术。在接触生成艺术之前,我一直觉得CSS就是“给HTML化妆”的工具,调个颜色、排个版、做个悬停效果,任务结束。直到有一次我无聊地写了十几个div,把每个动画的延迟错开0.1秒,刷新页面那一刻,屏幕上居然出现了一组像海浪一样有节奏的光晕。从那天起,我开始认真对待CSS生成艺术这件事。
所谓CSS生成艺术,简单说就是用层叠样式表本身的能力——渐变、混合模式、动画、变换、滤镜——去创造非人工逐帧绘制的视觉作品。它能做海报背景、动态壁纸、炫酷的加载动画,也能变成一套可以实时调节参数的视觉系统。这篇文章想跟你聊聊我是怎么用纯CSS做生成艺术的,从思路到实操,从踩坑到优化,尽量让零基础的朋友也能跟着跑起来,有点基础的朋友也能榨出点新想法。
1. 生成艺术的本质:为什么用CSS做视觉创作
1.1 从“布局工具”到“创作媒介”
很多人没意识到,CSS的底层设计已经远超“布局工具”的范畴。它包含一套完整的视觉描述语言:渐变可以表达颜色场,变换可以表达空间关系,动画可以表达时间序列,混合模式可以表达光与物质的交互。你把这些能力组合起来,并不是在“画”一张图,而是在“定义一套规则”让浏览器帮你在每一帧生成画面。这其实就是生成艺术最标准的定义:给系统一套规则,让视觉自己长出来。
我最早从Canvas和WebGL接触生成艺术,写过很多用数学公式驱动的粒子系统。但后来发现,CSS在很多场景下是更轻量的生成媒介。它不需要你手动管理画布上下文,不需要requestAnimationFrame循环,甚至不需要JavaScript,只要给元素配上动画属性,浏览器就会自动完成插值和绘制。这种“声明式生成”的方式,让视觉创作的门槛低了一大截。
1.2 CSS生成艺术的优势与边界
优势非常明显。第一,实时性高:CSS动画由浏览器合成器直接处理,很多场景比JavaScript操作DOM更流畅。第二,参数化友好:CSS变量可以让你像调旋钮一样调整颜色、尺寸、时长,不需要改逻辑。第三,易于分享和复用:一段几百字节的CSS就能生成非常复杂的视觉效果,贴到CodePen或者聊天窗口里就能被直接执行。第四,与页面集成自然:它是Web平台的原生能力,不用引入任何依赖。
但也有边界。CSS不适合做真正意义上的物理模拟或粒子碰撞,也不适合逐像素控制。你很难在纯CSS里实现复杂的贝塞尔曲线路径动画(除非用SVG或者CSS offset-path,但那又另说)。当视觉复杂度极高、需要大量canvas像素级操作时,还是得用WebGL。我的经验是:CSS生成艺术适合做“有规则感、几何感、节奏感”的作品,尤其适合抽象几何、流动渐变、重复波纹这类风格。
1.3 需要的基础知识栈
如果要入门CSS生成艺术,其实不需要太深的后台知识,但有几块必须扎实:选择器、盒模型、定位、Flexbox/Grid布局,这是组织元素的基础;transform、border-radius、clip-path,这是塑造形态的基础;颜色、渐变、滤镜、混合模式,这是构建色彩关系的基础;animation、transition、@keyframes,这是赋予时间变化的基础。
CSS变量也强烈建议熟练掌握,它是让生成艺术“可调控”的关键。你会发现,只要变量设计得好,一个作品就能衍生出无数变体,这本身就很有“生成”的味道。接下来我会从工作台搭建开始,一步步把这套知识串起来。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 从零开始搭建一个CSS生成艺术工作台
2.1 开发环境与调试工具
做CSS生成艺术,最核心的开发环境其实就是浏览器本身。我平时习惯用Chrome DevTools,因为它有很好的实时编辑能力。你只需要打开开发者工具。找到“Styles”面板,就能直接修改CSS并立刻看到效果。这种实时反馈对生成艺术非常重要,因为很多视觉效果不是一次就能写对的,需要反复微调参数。
我还经常用CodePen这类在线编辑器,新建一个Pen,左边写HTML,右边写CSS,中间直接出效果。它的好处是方便分享和多人协作,也内置了Autoprefixer和Babel兼容处理,能省去不少环境配置功夫。如果做稍大一点的项目,我会在本地用Vite搭一个简单的纯静态工程,因为Vite对CSS和PostCSS的支持很顺手,还能通过热更新快速预览。
2.2 代码组织与工程化思路
纯CSS写艺术,最忌讳把所有东西堆到一个文件里。我遇到过的坑是:一开始图省事,把所有的渐变和动画都写在几个大选择器里,结果后面想调整参数,得在一大段代码里找半天,视觉参数和结构逻辑混在一起,极其痛苦。
后来我总结了一套简单的组织方式。首先,把可调节的视觉变量统一抽到:root下的CSS变量里,比如主色、副色、动画时长、粒子数量、基础尺寸。其次,把单个视觉元素封装成独立的类,例如.ring、.beam、.grid,每个类只负责自己的属性和动画。最后,用HTML结构来表达“组合关系”,用CSS类来表达“视觉变化”。这样代码读起来就像在描述一个视觉系统:一个容器里装很多个ring,每个ring的动画延迟不同,整体就产生了波纹扩散。
2.3 用CSS变量搭建设计系统
CSS变量不只是为了方便,它直接决定了作品的可扩展性。比如我做一个动态波浪背景,把波峰高度、波纹速度、颜色透明度都抽成变量:
css复制:root {
--wave-height: 40px;
--wave-speed: 6s;
--wave-color: rgba(80, 200, 255, 0.5);
}
这样后续想换风格,只改变量值就行。甚至可以通过JavaScript修改CSS变量,让用户拖动滑块实时调节效果,这就是交互式生成艺术的基础。
选变量名的时候,尽量不要用颜色名或语义化的盒子名,而是用“视觉维度”来命名,比如--ring-size、--glow-intensity、--stagger-step。这样代码的使用者看到变量名,就能知道它在控制什么,而不是被具体元素绑死。这是从“写页面”到“设计生成系统”思维转变很重要的一步。
3. 核心技法拆解:如何用代码生成视觉节奏
3.1 颜色生成:渐变、混合模式与调色板
颜色是生成艺术里最有情绪的部分。CSS生成颜色最常见的基础工具是linear-gradient、radial-gradient和repeating-linear-gradient。它们能创建平滑的颜色过渡,再结合背景尺寸和位置,可以形成丰富的色彩场。比如你可以用多重背景叠加的方式,把两层不同透明度的渐变叠在一起,产生类似印刷套印的层次感。
混合模式也是生成艺术的大杀器。mix-blend-mode可以让元素与它下面的内容进行颜色运算,比如overlay、screen、difference。我经常用difference模式做实验,两个叠加的渐变图案会在相交区域产生奇妙的颜色反差。这种“颜色之间的相互作用”是手绘很难精确控制的东西,但CSS只需要一行属性就能模拟出来。
调色板方面,我建议先设计好三到五个主色,然后用color-mix()函数在代码里动态生成同色系深浅变体。比如color-mix(in srgb, var(--primary) 30%, white)就能得到主色提亮30%后的颜色。这会比在编辑器里手动取色再硬编码要灵活得多。
3.2 形态生成:几何图形与重复布局
形是视觉的骨架。纯CSS能画的几何形态其实比想象中丰富。border-radius可以做出圆和胶囊;clip-path可以切出三角形、多边形、菱形,甚至任意路径;transform: rotate和skew可以改变元素姿态;box-shadow可以复制影子并平移,形成多层轮廓。
真正让它“生成感”爆发的,是把这些基础形态放进重复布局里。我经常结合Flexbox和Grid,让一组子元素自动排布成环状、网格或螺旋状。配合nth-child选择器,可以对每一个子元素施加不同的旋转角度或延迟时间。比如你有一个20个元素的圆环,想让它们均匀错开,只需要写:
css复制.circle-item:nth-child(1) { animation-delay: 0s; }
.circle-item:nth-child(2) { animation-delay: 0.2s; }
虽然手写20行很累,但你可以用SCSS循环,或者在JavaScript里动态生成这些规则。如果坚持纯CSS,也可以借助animation-delay: calc(var(--i) * 0.2s),再在HTML行内设置--i的值。这种方式最灵活,代码量也最小。
3.3 动态生成:动画、关键帧与贝塞尔曲线
动态效果是生成艺术最吸引人的部分。CSS动画的核心是@keyframes,它定义了一个属性从起始值到结束值的变化阶段。很多人用动画只习惯写from和to两个状态,但在生成艺术里,我建议多用百分比分段,因为视觉节奏往往需要中间态的缓冲。比如一个呼吸变化的光圈,可以定义0%透明度0.3,50%透明度1,100%透明度0.3,这样效果更细腻。
动画的缓动函数也会极大地影响观感。默认的ease在很多场景下会显得生硬,我更常用cubic-bezier自定义曲线,让动画的前半段加速、后半段减速。比如这样:
css复制animation: ripple 3s cubic-bezier(0.25, 0.46, 0.45, 0.94) infinite;
如果你想要周期性循环,ease-in-out是常见的舒适选择。steps()函数则能制造出“帧动画”般的停机感,非常适合像素风格或机械感的生成艺术。
多个动画叠加也是一招。你可以给同一个元素定义多个用逗号分隔的动画,比如一个同时做旋转和缩放的效果,两种运动叠加在一起,会产生很复杂的视觉行为。这种“运动叠加”是生成艺术很重要的工作方法。
4. 实操案例:纯CSS打造一个涟漪光圈扩散动画
4.1 需求拆解与效果预览
接下来我把一个很实用的案例拆开讲:用纯CSS实现一个从中心向外扩散的涟漪光圈。这个效果特别适合做加载状态、雷达扫描、水波背景,也是很多CSS动效网站上常见的样式。它的核心需求有几点:第一,从中心向四周均匀扩散;第二,光圈的透明度随半径增大而降低;第三,多圈之间要有错峰,形成连续波的感觉;第四,整体循环往复、无感知断点。
我先说最终效果:页面上一个圆形区域,内部持续产生3到5圈半透明的圆环,圆环慢慢变大、变淡,然后消失,循环反复。看上去就像水滴落在水面后泛起的波纹。这个效果不需要JavaScript,只靠几个嵌套元素和CSS动画就能实现。
4.2 核心实现与参数计算
实现思路并不复杂。我先创建一个容器,然后在里面放几个同样大小的子元素span,每个span都使用border-radius: 50%变成圆形,并设置一圈半透明边框。每个span的动画都一样:从初始状态(透明、无缩放、边框可见)到结束状态(完全透明、放大数倍)。
我先给容器设置一个大小,这里用一个变量控制:
css复制.ripple {
--ripple-size: 200px;
position: relative;
width: var(--ripple-size);
height: var(--ripple-size);
border-radius: 50%;
}
然后给每个光圈元素设置绝对定位,铺满容器,并定义动画:
css复制.ripple span {
position: absolute;
inset: 0;
border-radius: 50%;
border: 3px solid rgba(64, 196, 255, 0.8);
animation: ripple-wave 3s linear infinite;
}
@keyframes ripple-wave {
0% {
transform: scale(0.2);
opacity: 0;
}
10% {
opacity: 1;
}
100% {
transform: scale(2.8);
opacity: 0;
}
}
这里我选择从scale(0.2)开始,而不是从0开始,是因为scale(0)会让元素缩成一点,在某些浏览器上容易出现闪烁或抗锯齿问题。从0.2开始,既保留了从中心冒出的感觉,又更稳定。
接下来是错峰。我给每个span设置一个递增的延迟,让它们均匀分布在动画周期内。因为动画周期是3秒,如果我放5个span,每个延迟间隔就是3除以5,也就是0.6秒。在CSS里可以直接写:
css复制.ripple span:nth-child(1) { animation-delay: 0s; }
.ripple span:nth-child(2) { animation-delay: 0.6s; }
.ripple span:nth-child(3) { animation-delay: 1.2s; }
.ripple span:nth-child(4) { animation-delay: 1.8s; }
.ripple span:nth-child(5) { animation-delay: 2.4s; }
效果的关键就在延迟值和周期的配合。只要五个延迟均匀覆盖整个周期,那么每一刻屏幕上都能看到状态不同的光圈,连续起来就是稳定扩散的波纹。如果你想要更柔和的波纹,可以把边框改成径向渐变背景,或者在光圈外叠加一层模糊滤镜。
4.3 扩展成可复用的组件
这个涟漪效果很容易扩展成组件。我习惯把它封成一个类名,加上CSS变量:
css复制.ripple {
--ripple-color: 64, 196, 255;
--ripple-count: 5;
--ripple-duration: 3s;
position: relative;
width: var(--ripple-size, 200px);
height: var(--ripple-size, 200px);
}
然后通过JavaScript或直接修改CSS变量,来控制颜色、圈数、速度。例如想让颜色变成金色,只需要设置--ripple-color: 255, 200, 0,并让span的边框颜色使用这个变量:
css复制.ripple span {
border-color: rgba(var(--ripple-color), 0.8);
}
这样这个组件就可以被复用到不同场景。我甚至把它直接做成了浏览器书签栏里的动态favicon效果,这算是另一个有趣的小玩法了。总之,把效果组件化,能让一次创作被反复使用,这也是生成艺术实用价值的一部分。
5. 进阶:把CSS艺术玩出花来
5.1 生成式纹理与噪点
很多时候,我们需要的不只是简单几何,而是一组有质感的纹理。CSS可以用repeating-linear-gradient和repeating-radial-gradient生成网格、条纹、点阵等规则纹理。而通过不同方向的渐变叠加,还能制造出类似编织物或细密噪点的效果。
我常用的一个噪点近似方法,是使用一个极小的重复径向渐变。比如:
css复制.noise {
background-image: radial-gradient(circle, rgba(0,0,0,0.15) 1px, transparent 1px);
background-size: 8px 8px;
}
这段代码会把页面铺满间距8像素的小圆点,看起来像噪点纹理。如果再叠加上动画缓慢移动背景位置,就能得到一种“微观粒子在流动”的视觉效果。这种纹理用在海报背景上很出效果。
5.2 交互式生成艺术
生成艺术如果加上交互,会更有生命力。最简单的做法是把CSS变量与hover、focus、:active等状态结合。例如鼠标悬停在一个方块上,方块内部的渐变角度随悬停状态改变,产生一种“被激活”的效果。
但更高级的玩法是结合JavaScript监听鼠标位置并修改CSS变量。比如我做一个圆环阵列,鼠标越靠近中心,圆环的旋转速度越快。只需要在JS里做几行代码:
js复制document.querySelector('.ripple').style.setProperty('--ripple-duration', `${Math.max(0.5, 3 - distance / 100)}s`);
这种方式非常轻量,却能让艺术作品“活”起来。另外,别忘了prefers-reduced-motion媒体查询。这是给有动晕问题的用户提供关闭动画的选项,也是一名创作者应该有的基本礼貌。做法很简单:
css复制@media (prefers-reduced-motion: reduce) {
.ripple span {
animation-duration: 0.01ms;
animation-iteration-count: 1;
}
}
5.3 与其他技术结合(Canvas/SVG)
虽然标题是纯CSS,但实际项目中我们没必要一根筋。我常常把CSS、SVG和Canvas混用,各取所长。SVG特别适合绘制复杂路径、弧线、曲线和文字轮廓,CSS则负责这些SVG元素的布局和动画状态。Canvas则适合做真正需要逐帧计算的粒子系统、流体模拟等。
一个比较典型的混用场景是:页面背景用CSS生成渐变和波纹,中心区域用SVG绘制一个复杂几何图案,鼠标交互区域用Canvas绘制实时粒子拖尾。这样三者配合,既保证了整体视觉的生成感,也让代码不至于陷入CSS对逐像素控制的乏力。CSS不是万能的,但它非常适合作为生成艺术的“组织层”和“表现层”。
6. 常见问题与性能优化实录
6.1 排查与调试技巧
我遇到最多的坑,就是动画元素凭空消失了。排查下来,最常见的原因是animation-name和@keyframes名称不一致,或者animation缩写里没写动画名称。CSS动画不像JavaScript会报错,它只会静默地不执行。所以遇到动画没动,第一件事就是打开DevTools,检查样式面板里有没有出现动画定义项。如果出现了但没动,多半是动画属性被覆盖,或者display变化导致的。
另一个常见问题是transform-origin行为诡异。用一个圆环从中心扩散时,如果默认的transform-origin不是预期位置,元素会沿着奇怪的中心点放大。解决办法是显式设置:
css复制transform-origin: center center;
对于绝对定位并设置了inset: 0的元素,中心点默认在元素本身的中心,一般没问题。但如果你用position: relative加margin布局,就很容易出现偏移。调试时建议先在元素上临时加一个背景色,能直观看到定位状态。
6.2 性能瓶颈和优化策略
CSS生成艺术最常见的性能问题是页面卡顿。这里我要特别提醒:filter: blur()和filter: drop-shadow()是性能杀手,尤其是在动画中频繁使用的时候,浏览器要逐帧做模糊计算,非常消耗GPU资源。如果必须用模糊,建议尽量固定模糊作用域,不要在大量元素上同时使用。
制作动画时,优先使用transform和opacity两个属性,因为它们能在合成器线程上运行,而像left、width、border-radius这些属性在动画过程中会触发布局和绘制,对性能很不友好。我一般把运动拆成三类:位移用translate,缩放用scale,透明度用opacity,这样浏览器能最大化地复用图层。
大量的动画元素也会吃内存。比如50个同时播放的CSS动画,每个动画都会创建多个图层。我的经验是:控制动画元素数量,能用一两个元素完成的视觉效果,就不要放十个。想要复杂的波纹效果,不一定需要很多DOM节点,也可以通过box-shadow的多层投影来模拟。这些技巧能明显提升效果演示时的流畅度。
6.3 踩坑记录
最后分享几个我实际踩过的坑。
第一个坑是过度使用filter: blur()做光晕效果。刚开始我觉得模糊叠加起来特别有氛围感,结果页面一加载,风扇转速直接起飞。后来我用径向渐变代替了模糊光晕,用radial-gradient(circle, rgba(...), transparent)来模拟柔光,性能和效果都更好。
第二个坑是动画延迟设置不当造成“首屏空白”。比如动画周期6秒,而最后一个元素的延迟是5秒,那么用户刷新页面后,前5秒画面里只有一个默认状态的光圈,看起来很单薄。解决办法是让初始状态也包含一部分动画状态,或者把延迟设置为负值:animation-delay: -2s可以让动画立即从中间某一帧开始,这样首屏看起来就是完整的。
第三个坑是不同浏览器的兼容性差异。mix-blend-mode在一些老旧浏览器和特定GPU组合下表现不稳定,color-mix()也是新特性。如果你要给大量用户看,建议在关键位置做降级方案,比如先设置一个基础颜色,再在这行之后写新特性的样式,浏览器不识别就自动跳过。
我现在的习惯是:每个效果都先梳理成最小可验证单元,单独跑通了再组合。这样每次出问题,我都能快速定位。CSS生成艺术最大的乐趣,就在于你可以用非常少的规则,创造出极其丰富的视觉变化。这个领域还有很多可以探索的组合,比如把生成艺术作品通过@property注册自定义属性,实现真正的CSS变量动画,这会让代码的表达力更上一层楼。
