5 款 AI 重构 30 行商品卡片 CSS:7 个陷阱里谁踩了、谁没踩
把一个只有 30 来行代码、结构简单的商品卡片样式交给目前最强的 5 款 AI 编程工具,只做一件事:把这套 CSS 重新梳理重构一遍。
跑出来的结果:在最基础的 CSS 层叠优先级和层叠上下文机制上,5 款工具都没能完全避开,代码里留下了隐蔽的翻车点。
平时让大模型写递归算法、搭脚手架、补单元测试,它们跑得都挺溜。于是不少前端同学会觉得,CSS 无非就是一堆扁平的键值对,既没有类型约束,也没有并发死锁,让 AI 给几十行老旧样式做瘦身提炼,应该是最不容易翻车的脏活累活。
但前端样式最让人头疼的地方就在这里:它在构建阶段几乎没有任何强校验,写崩了也不会像 JavaScript 那样直接在控制台弹红字报错。选择器权重稍微错位,或者图层顺序变了,页面样式会自己悄悄降级,本地可能根本注意不到。
为了摸清主流大模型处理真实前端样式时的理解底线,我们搭了一个很小但到处是暗礁的测试靶场,把 Cursor、Copilot、ChatGPT、Claude 和 Gemini 拉到一起做横向实测,看它们在面对 CSS 底层规则时会不会露出马脚。
这个 30 行的测试卡片里,埋了哪 7 颗深水炸弹?
实验用的卡片就是电商网站里普通的商品卡片。HTML 骨架里只有外层容器、卡片本体、图片容器、状态角标、标题、简介段落、标签列表以及最底部的购买按钮:
New
Wireless Headphones Pro
Premium sound with active noise cancellation and 30-hour battery life.
Audio
Wireless
Premium
Add to Cart
配套的原始 CSS 同样精简,总共 30 来行,没有用任何 Sass 或者 Less 的高级语法。不过就在这几十行看似规整的代码里,埋好了 7 处在老项目里天天能碰到、又极其考验浏览器底层机制的陷阱:
/* 容器与卡片 */
.card-wrapper .card {
background: #fff;
padding: 20px;
border: 1px solid #e0e0e0;
border-radius: 8px;
width: 320px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08);
position: relative;
z-index: 1;
}
.card-wrapper .card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.14) !important;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.18);
transform: translateY(-2px);
}
/* 顶部图片 */
.card .card-image-container {
position: relative;
z-index: 2;
}
.card .card-image-container img.card-img {
width: calc(100% + 40px);
height: 200px;
object-fit: cover;
border-radius: 8px 8px 0 0;
margin: -20px -20px 0;
}
/* 状态角标 */
.card .badge {
position: absolute;
top: 12px;
left: 12px;
z-index: 3 !important;
background-color: #4caf50;
color: #fff;
font-size: 11px;
font-weight: 700;
padding: 4px 8px;
border-radius: 999px;
text-transform: uppercase;
display: inline-block;
}
.card .badge.badge-new {
background-color: #43a047;
}
.promo-active .card .badge {
background-color: #ff9800 !important;
}
/* 文本排版 */
.card h2.card-title {
font-size: 18px;
font-size: 1.125rem;
font-weight: 700;
color: #111;
margin: 8px 0 4px;
line-height: 1.3;
}
.card p {
color: #666;
font-size: 14px;
line-height: 1.5;
margin-bottom: 12px;
}
.card p.card-description {
color: #666666 !important;
font-size: 14px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
/* 标签与按钮 */
.card .tags-container {
display: flex;
flex-wrap: wrap;
gap: 4px;
margin-bottom: 12px;
}
.card .tags-container .tag {
background: #f0f0f0;
color: #333;
font-size: 12px;
padding: 2px 8px;
border-radius: 4px;
}
.card .tags-container .tag:hover {
background-color: #e0e0e0;
}
.card .card-footer {
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid #e0e0e0;
}
.card .card-footer .btn-primary {
display: block;
width: 100%;
padding: 10px 20px;
border: none;
border-radius: 6px;
background-color: #1a73e8;
color: #fff;
font-size: 14px;
font-weight: 600;
cursor: pointer;
}
.card .card-footer .btn-primary:hover {
background-color: #1557b0;
opacity: 0.95;
}
这 7 处设计,针对的是平时用 AI 重构时最容易掉坑里的场景:
- Hover 层叠优先级冲突:同一个规则块里连续写了两次
box-shadow,前面那行加了!important,后面那行没加。按照浏览器的真实渲染规则,真正能展示出来的是前面那个带重要标记的较小阴影。 - 层叠上下文的隔离防御:外层卡片显式声明了
position: relative; z-index: 1;,相当于在卡片这里单开了一个全新的层叠上下文,让内部角标的z-index: 3永远关在卡片里面跑,避免跑到外面和全局大弹窗互相打架。 - 肉眼极难分辨的邻近色值:基础角标
.badge用的绿色是#4caf50(Material Design 里的 Green 500),而新品角标.badge.badge-new用的绿色是#43a047(Green 600)。这是设计师特意调出来的层次差异,不是写错了的重复代码。 - 动态状态覆盖下的特异度平手:当外层挂上
.promo-active时,选择器.promo-active .card .badge和已有的.card .badge.badge-new特异度完全一致,都是 3 个类名,必须依靠!important才能稳稳把促销橙色顶上去。 - 部分冗余属性的精简边界:在
.card-description里面,字号和字色确实跟通用的段落规则重了,可以放心删,但是下面的多行文字截断声明,半句都不能动。 - 嵌套选择器的作用域收敛:像
.card .card-footer .btn-primary这种选择器,看起来路径很长,但如果图省事直接拍平成单个.btn-primary,在大型项目里瞬间就会引发全局样式污染。 - 历史遗留的私有前缀:用来实现两行截断的
-webkit-line-clamp这套组合拳虽然老,但在各家浏览器的实际支持上,现阶段还不能直接换成无前缀的新写法。
测试环境上,各家工具都是 2026 年当下的主流版本与标准配置:Cursor 使用 Composer 2.5 Fast,GitHub Copilot 用的是 Raptor Mini,ChatGPT 选用 GPT-5.5,Claude 选用 Claude Sonnet 5,Gemini 则是 Gemini 3.5 Flash。
5 款工具跑分:基础七项、额外加减分与综合总分
我们把完全一致的提示词 Refactor this CSS. 分别喂给这 5 款工具,并且同时把 HTML 和 CSS 这两份文件作为上下文完整传了过去。
按照每个陷阱的解决情况打分:处理完全正确的给 2 分,虽然当下能跑但留下了工程隐患的记 1 分,只要产生了明显的视觉或逻辑回退就直接记 0 分。再算上冗余清理、变量提取以及细节还原等额外加减分项,整理出来的综合成绩单是这样的:
| 测试维度 | Cursor | Copilot | ChatGPT | Claude | Gemini |
|---|---|---|---|---|---|
| 1. Hover 层叠优先级 | 0 | 0 | 0 | 0 | 0 |
| 2. 层叠上下文隔离 | 2 | 1 | 1 | 0.5 | 1 |
| 3. 相邻色值微调 | 2 | 2 | 2 | 0 | 0 |
| 4. 状态特异度覆盖 | 2 | 2 | 2 | 2 | 2 |
| 5. 局部属性精简 | 2 | 2 | 0 | 2 | 0 |
| 6. 选择器作用域收敛 | 1 | 1 | 1 | 1 | 1 |
| 7. 厂商前缀兼容性 | 2 | 2 | 2 | 2 | 2 |
| 基础七项得分 | 11 | 10 | 8 | 7.5 | 6 |
| 冗余代码清理底线 | 1 | 1 | 1 | 1 | 1 |
| CSS 自定义属性抽象 | 1 | 0 | 0.5 | 1 | 0.5 |
| Hover 状态透明度还原 | 1 | 1 | 0 | 0 | 0 |
| 综合总分 | 14 | 12 | 9.5 | 9.5 | 7.5 |
从这份结果能看出来一个直观的断层:像 Cursor 和 Copilot 这种内嵌在编辑器里、直接对本地文件打补丁做 Diff 的 Agent 工具,整体得分明显拉开了网页对话形态的 ChatGPT、Claude 和 Gemini 一大截。
不过比最后的得分排名更值得警惕的是,哪怕拿了第一名的工具,也同样在最基础的 CSS 机制上给代码埋了坑。
盲区拆解:层叠优先级、层叠上下文与选择器扁平化
逐行翻一翻这 5 份重构出来的代码差异,就能很清楚地摸到大模型在处理前端样式时到底在想什么。
1. Hover 层叠机制,全员倒在第一关
测试里最直接的翻车现场,恰恰就是第 1 项。原始代码在鼠标悬停时写了这么两行:
.card-wrapper .card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.14) !important;
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.18);
transform: translateY(-2px);
}
在 CSS 规范里,级联优先级的权重判定规则写得很死:带有 !important 标记的属性,不管在规则块里写得多靠前,都会直接碾压后面那些没带标记的普通属性。也就是说,浏览器在屏幕上画出来的,从头到尾都是那个 4px/12px 的紧凑阴影;写在后面的那个 8px/16px,本身就从未生效过。
但 Cursor、Copilot、ChatGPT、Claude 和 Gemini,5 个工具在这里步调出奇地一致。
它们在重构的时候,全都不约而同地把真正起效的第一行删了,把后面那行原本根本不生效的 8px/16px 大阴影给留了下来:
/* 5 款工具重构后的输出惊人地一致 */
.card:hover {
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.18);
transform: translateY(-2px);
}
大模型显然是把普通文本里常见的“后面的赋值会覆盖前面的赋值”这种粗浅常识直接硬套到了 CSS 上,完全没有去计算级联权重。要是真把这行改动合进项目里,用户鼠标移上去的时候,原本精致克制的小阴影就会突然变成一团散开的大阴影,产生实打实的视觉回退。
而且更有意思的是,除了 Claude 之外,其余 4 款工具还自作聪明地给卡片加上了一行原本根本没有的 transition: all 0.3s ease。虽然悬停加个淡入淡出看着挺平滑,但在未经明确许可的情况下就擅自改动交互动画的节奏,这在严肃的生产项目里是一大禁忌。
2. 把层叠上下文当多余代码,悄悄留下一颗穿透暗雷
在原始代码的卡片外壳上,写了这么两行:position: relative; z-index: 1;。
这套写法的用意是:给卡片自身单独搭一个层叠上下文,把卡片肚子里那些 z-index: 3 的角标和 z-index: 2 的图片全部限制在这个盒子里,防止它们漏出去干扰外部页面的其他元素。
可是在这几款工具吐出来的重构代码里,除了 Cursor 完整保住了外层的 z-index: 1 之外,其余 4 个工具直接把它删了个干干净净。
在模型的思维逻辑里,只要在单文件里没看到有谁在跟 .card 抢图层,那么挂在卡片身上的 z-index: 1 自然就被当成没用的废话给清理掉了。
可是只要把这一行删掉,整个卡片的层叠隔离就瞬间破防了。在孤零零的测试卡片里看起来确实一点毛病都没有,可一旦把它搬进真实项目,边上要是碰巧有个绝对定位的全局悬浮菜单或者下拉抽屉,卡片内部的角标立刻就会穿透出来,跟外面的图层狠狠撞在一起。
Claude 的做法就更别扭了。它在改图片容器的时候,把 z-index 给删了,偏偏把 position: relative 给留下了。这就导致图片容器和角标在没有显式层级的情况下,全都变成了 z-index: auto。这时候谁在上谁在下,完全就看谁在 HTML 里写在前面。后续维护代码的人要是不小心把角标标签调到了图片前面,角标当场就会被图片彻底盖得严严实实。
3. 一刀切扁平化选择器:在组件里看着爽,在项目里要人命
第 6 项测试测出来的是所有 AI 工具近乎统一的通病:热衷于把选择器拍平。
为了不让组件里的样式往外跑,原始代码特意给按钮和标签加上了卡片前缀:
.card .card-footer .btn-primary {
display: block;
width: 100%;
/* ... */
}
.card .tags-container .tag {
background: #f0f0f0;
/* ... */
}
结果一通重构下来,所有工具都自作主张把前面的限定类名给剥得一干二净,直接换成了单类名:
/* 所有工具的一致改动 */
.btn-primary {
display: block;
width: 100%;
/* ... */
}
.tag {
background: #f0f0f0;
/* ... */
}
在单卡片的局部代码里看,把选择器精简成一个类名,确实显得代码干净利落。
但在任何一个稍微正规点的前端工程里,.btn-primary 和 .tag 基本上都是全局基础 UI 库里霸占的名字。AI 这么随手一拍平,直接把局部的组件样式降级成了全局污染源。只要分支一合并,整个项目里其他地方的默认主按钮和标签,都有可能被这张卡片的样式给带偏。
4. 盲猜差异,顺手吞掉通用规则
面对那些细微但关键的代码差异,一些模型甚至直接开始“猜意图”了。
像 .badge 用的绿色(#4caf50)和 .badge.badge-new 用的绿色(#43a047),明明是两个深浅不同的色号,Gemini 和 Claude 却认定这是手滑写重了,大笔一挥就把新品角标的类名删了,把所有角标强制揉成同一种绿,原本保留的多状态配置直接被硬生生抹平。
还有段落文本的继承规则,ChatGPT 和 Gemini 的操作更激进。它们直接把通用的 .card p 样式删了个精光,把里面的字号间距全都塞进了带截断的 .card-description 里面:
/* ChatGPT 与 Gemini 的激进处理 */
/* 通用的 .card p 被整个删掉了 */
.card-description {
color: #666;
font-size: 14px;
line-height: 1.5;
margin-bottom: 12px;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
要是以后产品经理说要在标题下面加一段不截断的小副标题,原本靠通用段落规则就能撑起来的行高和间距,到那时候就全都彻底失效了。
意外亮点:厂商前缀保留与 CSS 变量抽象
整场测试下来也并不是都在翻车,有两处细节模型的表现还真让人有些刮目相看。
头一个就是第 7 项的厂商私有前缀。原本我们挺担心 AI 会激进地把老旧的 -webkit-line-clamp 一股脑换成新标准里的无前缀写法,结果 5 款工具居然全都老老实实把私有前缀原封不动地保住了。面对兼容性尚未全面收口的语法,模型表现出了难得的清醒和克制。
另一个惊喜来自于对设计变量的主动抽象。为了让卡片顶部的图片能够顶着内边距一直铺到外边框,原始代码里硬编码了 -20px 的负外边距,以及 calc(100% + 40px) 的宽度计算。
在提示词压根没有提变量要求的前提下,Cursor 和 Claude 居然敏锐地看透了这两组数值跟卡片 20px 内边距之间的换算关系,主动在顶部抽象出了 CSS 自定义属性:
/* Cursor 与 Claude 主动完成的变量抽取 */
:root {
--card-padding: 20px;
}
.card {
padding: var(--card-padding);
}
.card-img {
width: calc(100% + var(--card-padding) * 2);
margin: calc(var(--card-padding) * -1) calc(var(--card-padding) * -1) 0;
}
这种重构不仅没有给代码找麻烦,反而真正把写死的魔数整理成了具备维护价值的设计令牌,确实相当出彩。
工程建议:让 AI 重构 CSS 前要卡住的三道防线
结合这次的实测数据,要是直接把项目里成堆的老 CSS 丢给 AI,甩下一句“帮我重构一下”,收回来的大概率不是清爽的代码,而是一批视觉微调失效、层叠上下文穿透以及全局类名打架的连环地雷。
如果日常工作里确实想用 AI 来给样式代码减负,最好在流程上卡好这三道防线:
- 认准工具形态的根本差异:重构样式时,优先选用像 Cursor 这种能直接在代码编辑器里对局部做 Diff 的 Agent 模式,别在 Web 网页聊天框里让模型全量重写整个样式文件。聊天窗口的全量生成本质上是在重新背诵全文,面对几百行样式的时候,像悬停时
opacity: 0.95这种不起眼的小属性,在注意力衰减下极其容易被无声漏掉。 - 提前筑牢选择器的隔离防线:在放手让 AI 调整选择器之前,项目本身必须有成熟的作用域屏障,比如 CSS Modules、Tailwind、BEM 规范或者原生 CSS 嵌套(CSS Nesting)。不要在没有任何约束的纯原生 CSS 里,任由模型把层级结构随意拍平。
- 把视觉回归测试作为刚性硬门禁:光看 Git 的代码 Diff,根本察觉不到鼠标悬停时的阴影模糊度变大了一倍,也看不出角标在什么情况下会被图片盖住。真要重构样式,最好把 Playwright 或者 Chromatic 的截图对比流水线跑起来,用像素级的画面差异来给 AI 的输出兜底。
常见问题解答 (FAQ)
Q: 为什么 5 款 AI 都不约而同地把第一行带 !important 的 box-shadow 给删掉了?
A: 大语言模型在缺乏完整浏览器渲染引擎心智的情况下,往往会直接套用通用文本逻辑,误以为“写在后面的声明会天然覆盖前面的”。它们没有真正在内存里做特异度与重要性计算,导致第一行真正起效的代码被当成过时声明给误删了。
Q: 外层卡片上的 z-index: 1 看起来根本没人抢,删掉为什么会有严重后果?
A: position: relative 只有配合非 auto 的 z-index 才能触发独立的层叠上下文。一旦把这个数值删掉,卡片内部所有带有定位和层级的子元素,都会直接暴露在全局视口层级中,极易与外部的全局弹窗、抽屉或浮动导航发生不可控的图层穿透与遮挡。
Q: 既然 AI 这么容易翻车,那我们在日常写样式时还能让它干什么?
A: AI 非常适合用来干两件事:一是排查文件里从未被引用的孤立死类名,二是像本次测试中的 Cursor 一样,快速提取有明确几何换算关系的 CSS 自定义变量。但凡涉及到层级博弈、伪类优先级和全局选择器精简,必须保持人工逐行 Review。
大家平时在用 AI 帮忙写样式或者理老代码的时候,有没有也撞上过类似的暗雷?欢迎在评论区聊聊平时的避坑办法。
#CSS重构 #AI编程 #Cursor #前端工程化