CSS progress() 函数来了,以后做响应式排版又多了一个神器
CSS `progress()` 函数来了,以后做响应式排版又多了一个神器
导语
做前端的都知道,响应式排版(Responsive Typography)一直是个”能做但很麻烦”的事。以前你要么写一堆 @media 断点,要么用 clamp() 算个线性插值公式,要么上 JS 计算。现在好了,CSS 官方终于出手了——progress() 函数正式进入 Chromium(Chrome 138+)。它的作用很简单:把一个值映射成 0 到 1 的进度,然后你想怎么用就怎么用。这玩意儿到底是啥?能解决什么问题?我今天拿几个实际场景测了一下。
核心内容
问题:响应式排版为什么这么烦?
先看一个最常见的场景:你想让一个标题在手机端 1.5rem、桌面端 3rem,中间平滑过渡。
以前主流做法有两种:
方案 A:写断点,分三段
h1 {
font-size: 1.5rem;
}
@media (min-width: 640px) {
h1 { font-size: 2rem; }
}
@media (min-width: 1024px) {
h1 { font-size: 3rem; }
}
问题:视觉上突兀,每次断点处”跳变”,而且中间值不可控。
方案 B:用 clamp() 算线性插值
h1 {
font-size: clamp(1.5rem, 1rem + 2vw, 3rem);
}
这是目前推荐的做法,一行搞定。但问题在哪?clamp() 底层就是线性插值,公式 min + (max - min) * progress 得你自己算。vw 单位一旦遇到容器查询(Container Query)场景,你就得套一层 cqw,脑子里还得想比例公式,不够直观。
方案 C:JS 计算,监听 resize
function calcFontSize() {
const vw = window.innerWidth;
const progress = Math.min(1, Math.max(0, (vw - 640) / (1200 - 640)));
document.querySelector('h1').style.fontSize = `${1.5 + 1.5 * progress}rem`;
}
能解决,但性能开销、无闪烁处理、resize 节流……一套下来代码量不小。
所以核心问题就几个字:把范围值映射成百分比这件事,应该 CSS 自己做,不该交给开发者手算或 JS 算。
方案:`progress()` 函数来了
progress() 的语法非常简单:
progress(, , )
它返回一个 0 到 1 的数字:
progress(300, 0, 1000)→ 返回值是0.3progress(50px, 0px, 100px)→ 返回值是0.5progress(50%, 30%, 80%)→ 返回值是(50% - 30%) / 80% = 0.25
三个参数的计算值类型必须一致——不能 progress(3s, 0px, 100px)。
返回值 0 到 1,然后你可以在 calc() 里用它做任何计算:
width: calc(100px + 200px * progress(80cqw, 150px, 800px));
这下好了,你不用再手算线性插值公式了,CSS 自己帮你搞定映射。
实战一:容器查询下的响应式字体
这是个真实场景。一个 Card 组件,卡片宽度从 150px 到 800px 变化,图片尺寸想在 100px~300px 之间自适应。
以前你可以这么写在 clamp() 里:
img { width: clamp(100px, 12.5% + 75px, 300px); }
不写注释的话,下一个人看到这行代码根本不知道 12.5%+75px 是哪来的。
现在用 progress():
.container {
container-type: inline-size;
padding: 10px;
border: dashed gray;
resize: both;
overflow: hidden;
}
.container img {
width: calc(100px + 200px * progress(80cqw, 150px, 800px));
}
逐行解释:
80cqw是容器宽度的 80%progress(80cqw, 150px, 800px)算出”当前容器宽度在 150px~800px 范围内的进度”- 然后把进度乘上
200px,加上100px基值,最终图片宽度在 100px~300px 之间变化
代码的可读性和可维护性都大大提升。你在代码里能直接看到”范围”和”目标值”的逻辑,而不是一个奇怪的百分比公式。
实战二:自动配色(纯 CSS 方案)
这个用法简直是”骚操作”级别。
以前你要根据背景色亮度自动切换文字黑白,得用 JS 或一个取巧的 opacity 边界特性。张鑫旭在 2018 年写过一篇文章,利用 opacity 的边界特性实现。那方案很巧妙,但不够直观。
现在有了 progress(),加上 CSS 的 infinity 关键字,可以这么写:
:root { --lightness: 0.45; }
.card {
background: hsl(220, 50%, calc(var(--lightness) * 100%));
color: hsl(0, 0%, calc(100% * progress(
calc((0.5 - var(--lightness)) * infinity), 0, 1
)));
}
逻辑是什么?
(0.5 - var(--lightness)) * infinity:如果--lightness < 0.5(背景偏暗),结果是+infinity;如果> 0.5(背景偏亮),结果是-infinityprogress(+infinity, 0, 1)返回 1,progress(-infinity, 0, 1)返回 0- 所以文字颜色就是:暗背景 →
hsl(0,0%,100%)(白色),亮背景 →hsl(0,0%,0%)(黑色)
一行 CSS 实现了以前需要 JS 才能干的事。而且逻辑非常清晰:“如果背景亮度大于 0.5,文字用黑色;否则用白色”。
实战三:滚动驱动的进度敏感样式
虽然 scroll-timeline 已经有自己的进度机制,但 progress() 可以跟 CSS 变量组合,做一些更灵活的交互。
比如一个阅读进度条:
/* 假设你的 JS 把阅读进度写到 CSS 变量 --read-progress 里 */
.progress-bar {
background: linear-gradient(
to right,
#6366f1 calc(100% * var(--read-progress)),
#e2e8f0 calc(100% * var(--read-progress))
);
height: 3px;
transition: background 0.1s;
}
/* 也可以用 progress() 让颜色渐变的过渡更丰富 */
.progress-bar {
background: color-mix(
in srgb,
#6366f1 calc(100% * progress(var(--read-progress), 0, 1)),
#f97316
);
}
这里你以为 progress() 是多余的——因为 var(--read-progress) 本来就是 0~1——但它的真正威力在于当你的值不在 0~1 范围内时,比如你鼠标滚动像素值、元素偏移量、时间值等。
`progress()` vs `clamp()`:怎么选?
| 场景 | 推荐 |
|---|---|
| 简单限制上下界 | `clamp()` |
| 需要范围映射后做更多计算 | `progress()` + `calc()` |
| 容器查询内的响应式排版 | `progress()` + `cqw` |
| 简单的字体大小自适应 | `clamp()` 够用 |
| 复杂的多变量组合 | `progress()` |
一句话判断:如果只是”最小值/首选值/最大值”三件套,用 clamp();如果要把一个值转换成 0~1 的进度再去做其他计算,用 progress()。
兼容性
目前 Chrome 138+ 支持,Safari 和 Firefox 还在路上。
按 CSS 新特性进入 Chromium 到全面铺开的规律(对标 Container Queries、:has() 这些特性),保守估计 1-2 年内会达到 85% 以上的覆盖率。现在可以作为 Progressive Enhancement(渐进增强)手段使用:有 progress() 时体验更好,没有时回退到 clamp() 方案。
/* 回退方案 */
h1 { font-size: clamp(1.5rem, 1rem + 2vw, 3rem); }
/* 渐进增强方案 */
@supports (progress(100vw, 360px, 1200px)) {
h1 {
font-size: calc(
1.5rem + 1.5rem * progress(100vw, 360px, 1200px)
);
}
}
收尾
progress() 函数虽然现在还是个”玩具级”(Chrome only),但它的设计思路非常清晰:把”范围到进度的映射”这个高频需求收进 CSS 标准里,让开发者不用再手算线性公式或者写 JS 来干了。
至于下一步:如果你在做实验性项目,或者个人站点的体验升级,现在就可以在 Chrome 里玩起来。在生产环境的项目上,先用 @supports 做一层渐进增强,不破坏现有体验。等 Safari 和 Firefox 跟进了,你把回退代码一删,就完事了。
要我说,CSS 正在从一个”声明式样式语言”进化成一个真正的计算系统。从 calc() 到 clamp(),从 min()/max() 到 progress(),再到 random() 和三角函数——CSS 在吃掉越来越多以前 JS 才干的活。这对前端来说是好消息:更少的 JavaScript,更纯粹的表现层代码。
评论区
登录后可评论。












