首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >9 个新 CSS 特性,正在悄悄干掉你的 JS

9 个新 CSS 特性,正在悄悄干掉你的 JS

作者头像
前端达人
发布2026-07-29 14:07:01
发布2026-07-29 14:07:01
110
举报
文章被收录于专栏:前端达人前端达人

我拿一个「商品详情页」的需求,从头到尾走了一遍。原来要写三天的东西,现在两小时。


先说个事。

上周组里来了个需求,特别常规:电商商品详情页改版。产品经理列了个清单,你肯定见过——

  • 顶部图片能左右滑,底下要有小圆点
  • 页面往下滚,顶部导航要吸住,吸住之后加个阴影
  • 右侧目录跟着滚动高亮当前区块
  • 选支付方式的下拉框,要带图标和说明文字
  • 商家名字 hover 上去出个店铺卡片
  • 点「立即购买」弹确认框,点旁边空白能关掉
  • 订单状态不同,卡片边框颜色不一样
  • 推荐商品列表进场时依次淡入

排期给了三天。放在去年,这三天一点不宽裕——光轮播图我就得写 200 多行。

需求最后当然还是按老办法交的(原因下面会说)。但交完之后我不太甘心,周末拿这份需求当靶子,用 CSS Wrapped 2025 里的新特性在本地重做了一遍

结果是:上面八件事,七件半没写一行 JS。

我按做需求的顺序讲,每个特性先说人话、再上代码,每一节末尾都标了真实的兼容情况和降级方案——因为这才是决定你明天能不能用上的关键。

开始前提醒三句:

  1. 这批东西很新。只有一个已经是 Baseline(三大浏览器全支持),其余大部分只在 Chromium 系(Chrome / Edge)里能用。我在每一节开头都标了具体版本号。
  2. 里面有四个(commandcommandforclosedbyinterestfor)严格说是 HTML 属性不是 CSS,但它们跟这批一起发的、解决同一类问题,就一起讲了。
  3. 上线前一定去 Can I Use 或 MDN 再查一遍——下面的版本号是我 2026 年 7 月核对的,这批特性推进得很快。

先看一眼全景——左边是你熟悉的活儿,右边是现在谁来干:

代码语言:javascript
复制
     以前你写的 JS            现在归谁管            兼容情况
     ─────────────            ──────────            ────────
  ┌─ 点按钮弹确认框 ──────→ command/commandfor   ✅ 三大浏览器
  │
  ├─ 点空白处关掉弹窗 ────→ closedby             🟡 Chrome+FF
  │
  ├─ hover 出店铺卡片 ───→ interestfor          🟡 Chrome 142
  │
  ├─ 自己撸下拉框 ───────→ appearance:base-select 🟡 Chrome 135
  │
  ├─ 轮播箭头 + 小圆点 ───→ ::scroll-button 等    🟡 Chrome 135
  │
  ├─ 目录跟着滚动高亮 ────→ scroll-target-group   🟡 Chrome 140
  │
  ├─ 判断导航吸顶没有 ────→ scroll-state 查询     🟡 Chrome 133
  │
  ├─ 列表依次淡入 ───────→ sibling-index()      🟡 Chrome+Safari
  │
  └─ 按状态换卡片颜色 ───→ if() + attr()        🟡 Chrome 137/133

(🟡 = 目前主要是 Chromium 系,Firefox / Safari 还没跟上,需要降级方案。具体到每一个我下面都会说。)

行,开工。

第一件事:点「立即购买」弹确认框

需求最简单的一条,先干掉。

以前怎么写? 按钮上挂个 onclick,里面拿到弹窗元素,调 showModal()

代码语言:javascript
复制
<button onclick="document.querySelector('#confirm').showModal()">
  立即购买
</button>
<dialog id="confirm">确认下单?</dialog>

就为了「点一下弹个框」,你得写 JS。

现在怎么写? 两个属性,一行 JS 都不用:

代码语言:javascript
复制
<button commandfor="confirm" command="show-modal">立即购买</button>
<dialog id="confirm">确认下单?</dialog>

看懂了吗?commandfor 就是指一下我要操作谁,写目标元素的 id——用法和 <label for> 一模一样。command 就是说一下我要干啥

command 能填的值,其实就是你早就会的那几个 JS 方法,换成了字符串:

你写 command="..."

相当于以前的

show-modal

dialog.showModal()

close

dialog.close()

show-popover

el.showPopover()

hide-popover

el.hidePopover()

toggle-popover

el.togglePopover()

兼容性:整篇文章里唯一一个可以放心用的。 Chrome 135、Firefox 144、Safari 26.2 依次跟进,2025 年 12 月正式进入 Baseline「新近可用」。

(顺带解释下 Baseline:「新近可用」意思是三大浏览器的最新版都支持了;要等三年后才会变成「广泛可用」。所以如果你的用户里还有一批老设备,该做的降级还得做——不过这个特性的降级很简单,把 onclick 那行留着当兜底就行。)

好处不只是少打几个字。因为「谁点谁、点了开哪个」直接写在 HTML 上,后面接手的人一眼就能看明白,不用去 JS 里翻。而且——万一 JS 加载失败了,这个按钮照样能用

第二件事:点弹窗外面的空白,关掉它

这个体验特别常见,但每次都要多写两段代码:监听遮罩点击,再监听 Esc 键。

以前:

代码语言:javascript
复制
dialog.addEventListener('click', (e) => {
  if (e.target === dialog) dialog.close();   // 点到了遮罩
});
document.addEventListener('keydown', (e) => {
  if (e.key === 'Escape') dialog.close();     // 按了 Esc
});

顺便说,上面这段其实有个隐藏 bug。它靠「点击目标是不是 dialog 本身」来判断你点的是不是遮罩。但如果弹窗有内边距(padding),你点在那圈内边距上——那也算 dialog 本身——弹窗就莫名其妙关了。

这种边界情况,本来就不该由我们操心。

现在:一个属性搞定。

代码语言:javascript
复制
<dialog id="confirm" closedby="any">
  正在校验库存……
</dialog>

closedby 就三个值,字面意思:

  • any —— 点外面或者按 Esc,都能关
  • closerequest —— 只有按 Esc 能关
  • none —— 都不行,只能你自己写代码关(适合「支付中,别乱点」这种)

有个小知识点值得知道:不写 closedby 的时候,行为看你是怎么打开的。用 showModal() 打开的,默认就能按 Esc 关;非模态的默认哪个都不能关。所以真正新增的能力是 closedby="any" 里「点外面关掉」这一半。

两个监听器,换一个属性。

兼容性:Chrome 134+、Firefox 141+ 已支持,Safari 还没完整实现(Safari 26.2 挂出了 closedBy 属性但实现不完整,别被它骗了)。这条已经进了 Interop 2026 的名单,年内大概率补齐。

在那之前,稳妥的做法是留着旧的两段监听当兜底——它俩不冲突,浏览器支持 closedby 时你的 JS 顶多是空跑一遍。

第三件事:hover 商家名字,出个店铺卡片

这就是 Tooltip / 悬浮卡片,看着简单,写起来是最烦的之一。

以前得写多少? 鼠标移入、鼠标移出、键盘聚焦、键盘失焦——四个监听。还得加个定时器,不然鼠标从按钮移向卡片的路上,卡片会一闪就没。再给触屏用户凑一个降级方案。

写完你会发现,这一坨代码,跟业务半毛钱关系没有。

现在:

代码语言:javascript
复制
<button interestfor="shop-card">星野家居旗舰店</button>

<div id="shop-card" popover="hint">
  开店 6 年 · 好评率 98.7% · 48 小时发货
</div>

interestfor 的字面意思就是「对谁感兴趣」。用户鼠标悬停、键盘聚焦、或者在触屏上长按,浏览器就认为你「表达了兴趣」,自动把它指向的那个元素显示出来;兴趣结束就收起。定时器、防闪烁、触屏适配,浏览器都替你处理了。它在 <a> 标签上也能用,不只限于按钮。

还有个细节挺关键:配合的是 popover="hint",这是新的一种 popover 类型。它不会关掉你已经打开的别的浮层

这个特性听着抽象,但场景很实在:用户点开了一个下拉菜单,然后 hover 菜单里某一项想看提示——如果提示浮层把菜单挤没了,那就白瞎了。用 hint 就不会。

兼容性:popover="hint" 从 Chrome 133 起就有了,interestfor 在 Chrome 142 正式发布(不再需要开实验开关了,这点跟半年前的说法不一样)。Firefox 和 Safari 目前都还没有,所以生产环境用它必须配降级方案。

另外说句公道话:成熟的 Tooltip 库先别删。碰撞检测(快贴到屏幕边缘要自动翻到另一边)、可交互 tooltip(鼠标能移进去点里面的链接),这些它还接不住。

它接住的是那 80% 的「就是想显示一句提示」——而这 80%,恰恰占了你 Tooltip 代码的绝大部分。

第四件事:支付方式下拉框,要图标 + 说明文字

来到今天的重头戏之一。

我先问一句:你有多少次上组件库的 Select,或者自己用 <div> 硬撸一个下拉框,纯粹只是因为原生 <select> 长得丑、还改不动?

反正我是数不清了。

原生 <select> 一直是个「黑盒」——浏览器画的,你改不了。所以我们只能自己造:一个 div 当按钮,一个 div 当面板,再补上键盘上下键、回车选中、Esc 关闭、点外面收起、无障碍属性……造一个能用的下拉框,轻松两三百行。

现在,一行 CSS 就能把这个黑盒打开:

代码语言:javascript
复制
select,
::picker(select) {
  appearance: base-select;
}

appearance: base-select 是一个开关。打开之后,这个 <select> 就从「浏览器画的黑盒」变成了「你能随便改的普通元素」。

然后你想怎么改就怎么改:

代码语言:javascript
复制
/* 下拉面板 */
::picker(select) {
  border: 1px solid #ddd;
  border-radius: 12px;
  padding: 4px;
}

/* 每一个选项 */
option {
  display: flex;
  gap: 0.6rem;
  padding: 0.5rem;
  border-radius: 8px;
}

/* 当前选中的那个 */
option:checked {
  background: #eef2ff;
}

顺手还解决了一个老坑:下拉面板渲染在页面最顶层,不会被祖先元素的 overflow: hidden 裁掉一半。这个坑我信你踩过。同时浏览器还是会自动判断——下面空间不够就往上翻。

更爽的在后面:打开这个开关之后,<option> 里能塞富文本了。产品要的「图标 + 名称 + 说明」,直接写:

代码语言:javascript
复制
<select>
  <button>
    <selectedcontent></selectedcontent>
  </button>

  <option value="wx">
    <span class="icon">💬</span>
    <span class="name">微信支付</span>
    <span class="desc">推荐,免手续费</span>
  </option>
  <option value="ali">
    <span class="icon">💰</span>
    <span class="name">支付宝</span>
    <span class="desc">支持花呗分期</span>
  </option>
</select>

那个 <selectedcontent> 也是新的。它干的事很简单:把你选中的那个 option 的内容,原样复制一份到按钮里。所以下拉收起来之后,按钮上显示的也是「💬 微信支付」,而不是干巴巴一行字。

那按钮里我只想要图标和名称,不想要那行说明呢?藏掉就行:

代码语言:javascript
复制
selectedcontent .desc {
  display: none;
}

原生下拉框,自定义外观,常规场景不用组件库了。这个我们等了差不多十年。

兼容性:Chrome / Edge 135+(2025 年 4 月),Firefox 和 Safari 都还没有。

但这个特性的降级是全场最优雅的:不支持的浏览器直接忽略 appearance: base-select<select> 退回成原生下拉框——样子丑一点,功能一点不缺。所以哪怕现在就写,最差的结果也就是「部分用户看到的是系统默认样式」。这个代价我觉得可以接受。

第五件事:商品图轮播,带箭头和小圆点

来了,去年让我写了 200 多行的那个。

先想清楚一件事:轮播图难的从来不是"滑动"本身。滑动早就有 scroll-snap 了,几行 CSS 就能让它一屏一屏地吸附对齐。

难的是外面那一圈控件:

  • 左右箭头
  • 滑到第一张,左箭头要变灰
  • 底下的小圆点
  • 当前是第几张,对应圆点要高亮
  • 点圆点要能跳过去
  • 键盘方向键要能用
  • 屏幕阅读器要能念出来

这一圈,CSS 现在能直接给你生成。 结构上你只需要写一个最朴素的列表:

代码语言:javascript
复制
<ul class="gallery">
  <li><img src="p1.jpg" alt="商品主图" /></li>
  <li><img src="p2.jpg" alt="细节图" /></li>
  <li><img src="p3.jpg" alt="尺码表" /></li>
</ul>

先是老朋友,让它能一屏一屏滑:

代码语言:javascript
复制
.gallery {
  display: flex;
  gap: 1rem;
  list-style: none;
  padding: 0;
  overflow-x: auto;
  scroll-snap-type: x mandatory;  /* 滑动时自动吸附对齐 */
}

.gallery > li {
  flex: 0 0 100%;
  scroll-snap-align: center;
}

到这为止都是老东西。下面开始是新的——两行 CSS,凭空长出左右箭头:

代码语言:javascript
复制
.gallery::scroll-button(left) {
  content: "◀" / "上一张";
}
.gallery::scroll-button(right) {
  content: "▶" / "下一张";
}

这里有两个特别值钱的点:

第一,它生成的是真按钮,能聚焦、能用键盘操作。而且滑不动的方向,浏览器自动帮你禁用——手写轮播里最烦人的那个「第一张时左箭头要变灰」,白送。

第二,注意那个斜杠。content: "▶" / "下一张",斜杠后面是给屏幕阅读器念的名字。视障用户听到的是「下一张」,而不是「黑色右指三角形」。这种事以前得自己补 aria-label,十次有八次会忘。

接着是小圆点。先告诉容器「在我后面生成一组标记」:

代码语言:javascript
复制
.gallery {
  scroll-marker-group: after;
}

然后给每一项定义它的标记长什么样:

代码语言:javascript
复制
.gallery > li::scroll-marker {
  content: "";
  width: 0.6em;
  height: 0.6em;
  border: 1px solid currentColor;
  border-radius: 50%;
}

/* 当前正在看的那一张,圆点填实 */
.gallery > li::scroll-marker:target-current {
  background: currentColor;
}

:target-current 就是「现在轮到你了」的意思。点圆点能跳过去,跳到哪个哪个亮——这套联动逻辑,浏览器全包了。

补一个细节:::scroll-button() 点一下滚动的距离是**可视区域的 85%**,不是精确一屏。想要严格一屏一屏走,就靠上面的 scroll-snap 兜住。

当然,自动播放、图片懒加载、埋点上报,这些还得你自己写 JS。但我那 200 多行里,真正属于业务的也就这三样。

兼容性:Chrome / Edge 135+,Firefox 和 Safari 都没有。

降级同样很体面:不支持的浏览器里,::scroll-button()::scroll-marker 就是不生成,你的列表退化成一个可以手指滑、可以触控板横滚的普通列表——没有箭头和圆点,但内容一条不少。对移动端来说其实影响很小。

第六件事:右侧目录跟着滚动高亮

文档站、详情页最常见的那个效果:滚到「商品参数」,右边目录里「商品参数」就变蓝。行话叫 scroll-spy。

以前基本都这么写:

代码语言:javascript
复制
const observer = new IntersectionObserver((entries) => {
  entries.forEach((entry) => {
    const link = document.querySelector(`a[href="#${entry.target.id}"]`);
    if (entry.isIntersecting) link.classList.add('active');
    else link.classList.remove('active');
  });
});
sections.forEach((s) => observer.observe(s));

看着挺人畜无害对吧?但一屏里同时出现两个区块的时候,它就开始抽了——两个都算「进入视口」,两个都高亮。

真要写对,你得算交叉比例、得设 rootMargin、还得处理「页面滚到最底部时最后一节永远高亮不上」这个经典边界。我敢说你线上那份,多半也没处理全。

现在。 HTML 就是最普通的一组锚点链接,一点不特殊:

代码语言:javascript
复制
<nav class="toc">
  <a href="#detail">商品详情</a>
  <a href="#spec">规格参数</a>
  <a href="#review">用户评价</a>
</nav>

<section id="detail">…</section>
<section id="spec">…</section>
<section id="review">…</section>

CSS 加两句:

代码语言:javascript
复制
.toc {
  position: sticky;
  top: 0;
  background: #fff;
  scroll-target-group: auto;   /* 关键就是这行 */
}

.toc a:target-current {
  color: #2563eb;
  font-weight: 600;
}

scroll-target-group: auto 的意思是:「把我里面这组链接,当成一组滚动标记来管」。然后 :target-current 就会命中你当前正在看的那一节对应的那个链接。

跟上一节的小圆点是同一套机制,只不过这次标记是你自己写的链接。

顺手再补两句体验优化:

代码语言:javascript
复制
html { scroll-behavior: smooth; }   /* 点目录平滑滚过去 */
section { scroll-margin-top: 4rem; }  /* 别被吸顶导航挡住标题 */

最舒服的是:结构一点没变,就是一组最普通的 <a href="#id">。JS 挂了、CSS 没加载,它退化成一个能用的锚点导航。这才是渐进增强该有的样子。

兼容性:Chrome / Edge 140+,Firefox 和 Safari 没有。

这个是全场降级最省心的一个——不支持的浏览器里,你的目录就是一组普通锚点链接,点了照样能跳,只是不会自动高亮。丢的是锦上添花,不是功能本身。 我个人觉得这个可以现在就上。

第七件事:导航吸顶之后,加个阴影

需求原话是:「导航吸住的时候加个阴影,没吸住的时候别加」。

听着简单,但难点在于——CSS 以前不知道自己「吸住了没」position: sticky 是浏览器内部行为,样式层面感知不到。

所以以前的解法要么是监听滚动事件加节流,要么是在导航上面放一个隐形的「哨兵」元素,用 IntersectionObserver 盯着它有没有滚出视口。都挺绕。

现在 CSS 可以直接问了。

用法借用了容器查询的语法。两步:先把元素声明成「我要被查询滚动状态」,然后就能查了。

代码语言:javascript
复制
header {
  position: sticky;
  top: 0;
  container-type: scroll-state;   /* 第一步:声明 */

  > nav {
    transition: box-shadow 0.3s ease;

    /* 第二步:查询——吸顶了就加阴影 */
    @container scroll-state(stuck: top) {
      box-shadow: 0 2px 8px rgb(0 0 0 / 0.15);
    }
  }
}

能查的状态有四种,名字都很直白:

  • stuck —— 吸住了没(Chrome 133)
  • snapped —— 吸附对齐了没(Chrome 133)
  • scrollable —— 还能不能继续滚(Chrome 133)
  • scrolled —— 刚才往哪个方向滚的(Chrome 144 才有,比前三个晚一年,用之前留意下)

最后那个 scrolled 是做「向下滚隐藏导航栏、向上滚又冒出来」那种效果用的,属于新到不能再新的那一批。

这里有一条规则必须记住:你不能给容器本身设样式,只能给它的后代设。

这就是为什么上面例子里,阴影加在 > nav 上,而不是 header 上。第一次用的人 90% 都在这卡住过,包括我。

再看个用 snapped 的:横滑推荐位里,当前这张亮,其他的压暗。这个效果以前要靠监听滚动算位置:

代码语言:javascript
复制
.shelf {
  display: flex;
  gap: 1rem;
  overflow-x: scroll;
  scroll-snap-type: x mandatory;

  > div {
    container-type: scroll-state;
    scroll-snap-align: center;
    flex: 0 0 70%;

    > * { transition: opacity 0.5s ease; }

    /* 没被吸附对齐的(也就是不在中间的),压暗 */
    @container not scroll-state(snapped: x) {
      > * { opacity: 0.25; }
    }
  }
}

调试小贴士:写完发现啥效果都没有的时候,别急着怀疑自己的选择器。先去控制台跑一句:

代码语言:javascript
复制
CSS.supports('container-type: scroll-state')

返回 false,那是浏览器不支持,不是你写错了。这一句能省你半小时。

兼容性:Chrome / Edge 133+,Firefox 和 Safari 没有。 需要跨浏览器的场景,老老实实用 IntersectionObserver 加哨兵元素——这也是官方文档给的建议。

第八件事:推荐商品依次淡入

产品最爱提的效果:列表项一个接一个滑进来,不要齐刷刷一起出现。

以前怎么做? 要么在 CSS 里给每一项硬写一个下标,写十几行 :nth-child。要么更省事但更糟——用 JS 循环打内联样式:

代码语言:javascript
复制
document.querySelectorAll('.item').forEach((el, i) => {
  el.style.setProperty('--index', i);
});

问题是:列表一变,就得重跑一遍。分页加载、虚拟列表、筛选切换的场景下,你得到处补这行代码,漏一个地方动画就乱。

现在 CSS 自己知道「我是第几个」了。

代码语言:javascript
复制
.item {
  transition: opacity 0.25s ease, translate 0.25s ease;

  /* sibling-index() 从 1 开始,减 1 让第一项不延迟 */
  transition-delay: calc(0.1s * (sibling-index() - 1));

  @starting-style {
    opacity: 0;
    translate: 1em 0;
  }
}

sibling-index() 直接返回「我在兄弟节点里排第几」。第一项延迟 0 秒,第二项 0.1 秒,第三项 0.2 秒——错峰效果就出来了。

@starting-style 是入场动画的起点状态,意思是「我刚出现的那一瞬间长这样」。)

新增一项,它自己就带着对应的延迟滑进来,你什么都不用管。

还有个配套的 sibling-count(),返回兄弟节点总数。这俩一配,能解决一个真实的线上事故级 bug:

代码语言:javascript
复制
.item {
  /* 不管几项,整串动画总共就跑 0.5 秒 */
  transition-delay: calc(0.5s / sibling-count() * (sibling-index() - 1));
}

想想按老写法:每项延迟 0.1 秒,测试环境 5 条数据看着挺舒服,上线之后用户搜出 100 条——最后一项要等 10 秒才出现。这种「数据量一大动画就离谱」的 bug,线上不知道躺着多少。

sibling-count() 把总时长锁死,10 项还是 100 项,永远 0.5 秒跑完。

兼容性:这个比前面几个好一些——Chrome / Edge 138+,Safari 26.2 也支持了,只差 Firefox。

而且它天然自带兜底:不支持的浏览器里 transition-delay 那行整条失效,所有项一起淡入。效果打折,但不会坏。

第九件事:订单状态不同,卡片边框不同色

最后一个,也是「我用 JS 纯粹只为了改个样式」这件事的终极形态。

以前的套路你太熟了:读一下 data-status,判断一下,然后 classList.toggle() 换个 class。绕一大圈,就为了改个颜色。

CSS 现在能直接读 HTML 上的数据了。

先说 attr()。这个函数你可能见过,以前它只能往 content 里吐字符串,用途很窄。现在它可以带类型,用在任何属性上:

代码语言:javascript
复制
.tag {
  background: attr(data-color type(<color>), #999);
}
代码语言:javascript
复制
<div class="tag" data-color="#2563eb">进行中</div>

读这行:「去拿 data-color 这个属性,按颜色来理解它,拿不到就用 #999 兜底」。

关键是它是动态的。从 JS、从 Vue、从 React,随便哪改掉那个属性,样式立刻跟着变:

代码语言:javascript
复制
tag.dataset.color = '#16a34a';   // 样式马上变绿

样式逻辑住在 CSS 里,JS 只负责改数据。这个分工,比 classList.toggle() 干净太多。

(有个刻意留的口子:attr()不能用来拼 URL。因为属性里可能存着 token 之类的东西,让它流进网络请求太危险,所以设计上直接禁掉了。)

然后是 if()——就是 CSS 版的三元表达式

代码语言:javascript
复制
.layout {
  display: flex;
  flex-direction: column;  /* 老浏览器走这行兜底 */
  flex-direction: if(media(orientation: landscape): row; else: column);
}

以前为了改一个属性得开一整个 @media 块,现在写在值里就行。

两个一配,订单状态的需求就这么解决了:

代码语言:javascript
复制
.order-card {
  /* 把 data-status 读成一个关键字 */
  --status: attr(data-status type(<custom-ident>), unknown);

  border: 3px solid;
  border-color: if(
    style(--status: paid): #16a34a;
    style(--status: refunded): #dc2626;
    else: #9ca3af
  );
}
代码语言:javascript
复制
<div class="order-card" data-status="paid">已支付</div>
<div class="order-card" data-status="refunded">已退款</div>
<div class="order-card" data-status="pending">待处理</div>

后端返回什么状态,前端原样吐到 data-status 上,颜色自己就对了。没有 JS 判断,没有 class 映射表。

上面这种「比对具体值」的写法最稳。至于比大小,是这批里最晚落地的一块(Chrome 142 才随「样式查询范围语法」一起发布)。

有个坑必须知道:想让 CSS 把一个值当成真数字来比较,得先用 @property 报备一下,说清楚它是什么类型。否则 CSS 只当它是一串没意义的字符,比不了大小:

代码语言:javascript
复制
@property --progress {
  syntax: "<percentage>";
  inherits: false;
  initial-value: 0%;
}

.progress-card {
  --progress: attr(data-progress type(<percentage>), 0%);
  background: #f59e0b;
  background: if(style(--progress > 80%): #16a34a; else: #f59e0b);
}
代码语言:javascript
复制
<div class="progress-card" data-progress="30%">进度 30%</div>
<div class="progress-card" data-progress="90%">进度 90%</div>

(能比较的类型有限制:两边必须是同一种数值类型,<length><number><percentage><angle><time> 这几类。拿颜色比大小是不行的。)

兼容性:类型化 attr() 从 Chrome 133、if() 从 Chrome 137、比大小从 Chrome 142;Firefox 和 Safari 都还在开发中。

注意我上面每段都写了两行 background——第一行是普通值,第二行才是 if()。这不是手滑,是故意的降级:不认识 if() 的浏览器会直接丢掉第二行,留下第一行的兜底色。CSS 的容错机制这时候就是你的降级方案,不用写任何判断。

方向已经很明确了:那些你靠 classList.toggle() 干的事,CSS 正在一件件接过去。

所以,JS 被开除了吗?

没有,差得远。

真正的业务逻辑、调接口、状态管理、复杂交互编排,这些还是 JS 的主场,短期内看不到替代品。

但你冷静回想一下自己每天写的 JS,有多大比例其实不是逻辑,是"胶水"?

开弹窗、关弹窗、算滚动位置、高亮导航、根据一个字段切 class——这些代码没有任何业务价值,却要占 bundle 体积、要写测试、要在无障碍上补课,还要在某个周五下午莫名其妙地坏掉。

CSS Wrapped 2025 干的事,说白了就是平台把这部分管道工程收回去了

包更小,能坏的地方更少,无障碍默认更好。还有我最看重的一条:JS 加载失败的时候,这些东西照样能用。

但也得把话说完整。

回到开头那个需求——它最后为什么还是按老办法交的?因为公司的兼容基线要覆盖 Safari 和一部分老设备,而这九个里只有 command / commandfor 一个是三大浏览器都支持的,其余全是 Chromium 独苗。

所以现阶段的现实做法,我觉得是这样:

可以现在就用(降级代价小,退化后功能不缺):

  • command / commandfor——已 Baseline
  • appearance: base-select——不支持就退回原生下拉框
  • scroll-target-group——不支持就是不高亮,链接照样能点
  • sibling-index()——不支持就是一起淡入
  • if() + attr()——写两行,前一行天然兜底

可以在内部系统、Electron、企业后台先玩起来(这些场景你能锁定 Chromium):

  • 轮播那套、scroll-state 查询、interestfor

先观望

  • scroll-state(scrolled)(Chrome 144 才有)、if() 里的比大小(Chrome 142 才有)

我给自己的判断是:**这批东西现在最大的价值不是"删代码",是"知道未来两年这些代码要被删"**。你现在写的每一个轮播组件、每一个 scroll-spy,心里都该有个数——它是临时的。

想看完整清单,去 CSS Wrapped 2025 翻剩下那 13 个,细节配合 MDN 查。

本文所有版本号截至 2026 年 7 月,上线前请自行复核。

📋 兼容性速查表(2026 年 7 月核对)

特性

Chrome/Edge

Firefox

Safari

不支持时会怎样

command / commandfor

135+

144+

26.2+

✅ 已 Baseline

<dialog closedby>

134+

141+

❌ 未完整实现

点外面关不掉,留旧监听兜底

interestfor

142+

提示不显示,需 JS 降级

popover="hint"

133+

同上

appearance: base-select

135+

退回原生下拉框,功能不缺

<selectedcontent>

135+

同上

::scroll-button() / ::scroll-marker

135+

无箭头和圆点,仍可手滑

scroll-target-group

140+

不高亮,锚点仍可跳转

scroll-state() 查询

133+

样式不生效,用 IO 兜底

└ scrolled 状态

144+

同上

sibling-index() / sibling-count()

138+

26.2+

动画一起播,不错峰

类型化 attr()

133+

走前一行兜底值

if()

137+

同上

└ 比大小(><)

142+

同上

聊两句

如果现在让你从这 9 个里挑一个,明天就重构进项目,你选哪个?

  • Acommand / commandfor——三大浏览器都支持了,零风险,删弹窗胶水代码最爽
  • B:轮播图那套(::scroll-button + ::scroll-marker)——这玩意我是真写吐了
  • Cappearance: base-select——等了十年,就等它稳定
  • Dscroll-state 滚动状态查询——终于能干掉那堆滚动监听
  • E:我全都要,但公司要兼容的浏览器版本不允许(说的是不是你?)

评论区留下你的选项和理由。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2026-07-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 前端达人 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 第一件事:点「立即购买」弹确认框
  • 第二件事:点弹窗外面的空白,关掉它
  • 第三件事:hover 商家名字,出个店铺卡片
  • 第四件事:支付方式下拉框,要图标 + 说明文字
  • 第五件事:商品图轮播,带箭头和小圆点
  • 第六件事:右侧目录跟着滚动高亮
  • 第七件事:导航吸顶之后,加个阴影
  • 第八件事:推荐商品依次淡入
  • 第九件事:订单状态不同,卡片边框不同色
  • 所以,JS 被开除了吗?
  • 📋 兼容性速查表(2026 年 7 月核对)
  • 聊两句
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档