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.3
  • progress(50px, 0px, 100px) → 返回值是 0.5
  • progress(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(背景偏亮),结果是 -infinity
  • progress(+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,更纯粹的表现层代码。

评论区

0 条评论

登录后可评论。

查看完整榜单
查看完整榜单
查看完整榜单