先说结论:CSS变量确实无法直接用于repeat()函数来控制列数。这不是Bug,也不是浏览器兼容问题,而是CSS规范层面明确限制的规则,目前没有任何变通余地。你可能会认为,既然变量能传入值,那应该也能把3这样的数字传进去,但实际情况比想象中更为严格。

为什么 repeat(var(--cols), 1fr) 总是不生效?
当浏览器解析grid-template-columns时,repeat()的第一个参数必须是一个字面量数字,比如直接写3。而var(--cols)只会被当作字符串处理,浏览器不会将其展开为数值——也就是说,它根本不会被当作数字来运算。结果就是,所有主流浏览器(Chrome、Firefox、Safari、Edge)会直接无视这整条声明,Computed面板里grid-template-columns要么为空,要么回退成initial。
常见的错误写法包括:
--cols: "3"(带引号)--cols: 3px(带单位)--cols: 3.0(小数)
统统无效,没有例外。更有意思的是,@property目前也无法绕过这个限制,Chromium、Firefox、Safari都未支持。最让人头疼的是,DevTools里能看到这行声明被画上删除线,但控制台却没有任何错误提示——很多开发者看到这里就误以为“写得没错”,然后花大量时间排查其他方向。
CSS变量真正能发挥作用的地方在哪里?
虽然repeat()的列数控制行不通,但CSS变量在其他几个关键场合是安全且被广泛支持的(Safari 14.1+、Chrome、Firefox、Edge都行):
- 间隙:
gap: var(--gap),但必须带单位。比如--gap: 16px✅,而--gap: 16❌。更规范的写法是拆成row-gap: var(--row-gap)和column-gap: var(--col-gap),避免被意外覆盖。 minmax()内部:minmax(var(--min-width), 1fr)完全OK,比如--min-width: 250px就能正常工作。calc()表达式:像width: calc(var(--gap) * 2)可以用,但calc(var(--cols) * 2)在多数浏览器里还是无效。
那有没有办法用变量控制列数?
如果非要让列数随变量变化,唯一的办法就是借助JavaScript:读取变量值,拼接成合法的CSS字符串,再通过内联样式写回去。具体操作上要注意几个细节:
- 变量要定义在目标网格容器上,比如
.grid { --cols: 3; }。挂在:root上虽然也能读取,但作用域不对齐会增加不必要的复杂度。 - JS读取时用
parseInt(getComputedStyle(gridEl).getPropertyValue('--cols').trim()),确保拿到的是干净的数值。 - 写入时推荐写成
gridEl.style.gridTemplateColumns = `repeat(${n}, minmax(0, 1fr))`,加上minmax(0, 1fr)能防止窄屏时列被过度压缩。 - 避免高频调用:每次设置都会引发重排,而Grid的布局计算成本比Flex高不少,在resize或滚动事件里使用要格外谨慎。
更推荐的做法:auto-fit + minmax()
其实很多场景下,你真正需要的不是“列数等于某个变量值”,而是“列数能根据容器宽度自动调整”。如果是后者,完全不需要JavaScript:
grid-template-columns: repeat(auto-fit, minmax(var(--col-min), 1fr))- 变量只控制最小列宽(比如
--col-min: 280px),列数由浏览器自动计算。 - 空轨道会被自动折叠,空间重新分配给有内容的列。
这种方式语义清晰、零JS、天然响应式,兼容性也比自定义变量写法好得多。
最后说一个容易被忽略的点:很多人尝试把变量塞进repeat()后发现布局消失,却找不到原因。更糟的是,有人用Grid模拟瀑布流时,没意识到DOM顺序和视觉顺序不一致,这对SEO和屏幕阅读器体验是个硬伤。设计之初就把这些理清楚,比后期修复要轻松得多。
