本文由体验技术团队申君健原创。
序言
近日发现 Chrome 官方技术平台 chrome.dev 发布了一篇极具价值的 CSS 技术总结文章,原文链接为:CSS-Wrapped-2025,Wrapped 单词在这里是打包,总结,回顾的意思。Chrome官方罗列了2025年的新增CSS和组件特性等,有需要的小伙伴可以关注一下。此外,Chrome 官方亦发布了 2024 年度的 CSS 特性回顾文章,链接为:CSS-Wrapped-2024,可以对照查阅。
《CSS Wrapped 2025》一文共分为三个核心章节,分别是可定制的组件(Customizable Components)、下一代交互(Next-gen Interactions)、优化的人体工程学(Optimized ergonomics)。 由于精力时间有限,本文先精读第一部分,有机会再分享后续部分。
2025 全年,Chrome发布的版本为 132~143,本文的特性也集中在这个范围,它有很多全新的概念,或者是去年CSS概念的一些延伸。前端人员总不能第一时间使用新特性,以兼容性为借口忽视新技术,我也是一样。所以借此文章中新特性为提纲,全面总结该特性的知识,补充我的一些理解和总结。同时每一个新特性还准备一句话的解释和完整示例,方便大家快速了解。Chrome 143升级好了吗,开始带你飞!
一、命令调用器 (Invoker Commands)
一句话的解释
命令调用器是通过Button元素,向Dialog,popover元素或任意元素上触发一个动作命令。完整示例
命令调用器特性兼具声明式语法的高可读性优势,且能有效减少 JavaScript 代码的编写量。在该特性中,Button 元素被定义为 “命令源”,接收命令执行的元素则为 “命令目标”。
命令源:⭐仅 Button 元素才允许当命令源,它添加以下属性:
- 【attr】commandfor: 属性值为命令目标元素的 id,用于关联对应的命令目标。
- 【attr】command: 用于指定点击 Button 元素后触发的命令动作,其属性值说明如下:
| 命令 | 行为目标 | 等效js | 备注 |
|---|---|---|---|
| show-modal | 打开dialog | dialog.showModal() | |
| close | 关闭dialog | dialog.close() | |
| request-close | 请求关闭dialog | dialog.requestClose() | 可取消: ev.preventDefault() |
| show-popover | 打开popover | el.showPopover() | |
| hide-popover | 关闭popover | el.hidePopover() | |
| toggle-popover | 切换popover | el.togglePopover() | |
| –any-command | 自定义命令 | - | 事件名必须 – 打头 目标上监听command事件 非冒泡,可取消的事件 |
- 【prop】command: 同上
- 【prop】commandForElement: 同 commandfor ,值为HTMLElement对象。
命令目标: 通常是 dialog, popover元素,为它们添加一个事件:
- 【event】command: 触发在目标元素上的事件。 其中事件参数
event.command是命令值。
兼容性
支持chrome135+ ff144+, polyfill 方案。
总结
- 该特性的核心价值不仅在于减少 JavaScript 代码量,更在于实现了
更好的可读性,更好的语义化,更好的AI识别。Dialog元素是存在较久的冷门标签,Popover API是近两年的新特性,之前操作他们必须通过Javascript代码。 - 该特性也是一个微型的通知系统,某些程度上可以代替
new CustomEvent的使用。同样的,更好的可读性,参见图片翻转的示例。 - 命令源只能是Button,某种程度上限制了它的使用。
二、对话框轻量关闭(Dialog Light Dismiss)
一句话的解释
继 Popover Api 引入 Light Dismiss 之后,Dialog 也支持了它完整示例
Light Dismiss 直接翻译就是轻量关闭,友好关闭,具体是指通过点击 ::backdrop 区域、按下 Esc 键即可触发目标元素自动关闭的交互行为。Light Dismiss同样的具备声明式可阅读性,还能避免Javascript的使用。
<dialogclosedby="none">不触发关闭</dialog><dialogclosedby="closerequest">接受 esc或其它js触发</dialog><dialogclosedby="any">接受任何触发</dialog>Light Dismiss 通常是用户在交互过程中预期的默认行为,这一设计不仅体现了 Chrome 对用户使用体验人体工程学的关注,更彰显了其对开发者开发体验人体工程学的重视。开发者无需进行额外开发,即可获得预期的合理结果。延伸了解它的一些细节:
- closerequest 与 any 的相比,它不接受
点击::backdrop区域关闭;此外移动端的手指侧滑或导航回退也会触发closerequest的行为。 - dialog.requestClose()在dialog元素上触发
cancel 和 close事件, 而dialog.close() 只触发close事件。 在cancel事件中执行ev.preventDefault()可阻止关闭。 - dialog元素没有open事件,但它有
toggle, beforetoggle事件, 用来监听打开关闭。 事件对象的oldState,newState用来判断切换的方向。 此外,只有dialog 和 弹出层支持这2个事件名。
兼容性
支持chrome134+ ff141+, polyfill 方案。
延伸理解 Popover API 的Light Dismiss
<buttonpopovertarget="mypopover"popovertargetaction="toggle">切换显示</button><divid="mypopover"popover>这是一个 auto 弹出层</div>命令源:⭐仅 Button 元素和Input(type=button)才允许当命令源,它添加以下属性:
- 【attr】popovertarget: 其值为popover元素id
- 【attr】popovertargetaction: 点击的命令动作,其值为: ‘hide’ | ‘show’ | ‘toggle’
触发popover还可以用传统的Javascript, 或者Button的commands 模式,比如: el.showPopover()
popover层: 任意添加了 [popover] 属性的元素
- 【attr】popover: 设置元素为一个弹出层,它最早支持以下2个值:
- auto: 自动模式,也是默认值。 auto即符合Light Dismiss默认关闭行为。同一个页面上,auto类别的元素只能显示一个。
- manual: 手动模式。必须显示的声明popovertargetaction,或调用Javascript函数才触发,比如:el.showPopover()。同一个页面上,manual类别元素可显示多个。
参考完整示例,对比 Dialog 与 Popover API :
- 都支持Invoker Commands 和 Light Dismiss
- 都支持Javascript控制和声明式表达: dialog元素的
closedby和 popover元素的popover属性 - 都会产生一个
Top Layer, 无须z-index就能置顶元素,且不受父元素的position影响。 - 命令源和命令目标之间会隐式的产生aria-details关联aria-expanded,用于触发焦点导航等。
Popover API的这些特性兼容性为:chrome114+, ff125+
三、增强Popover (popover=“hint”) 与 兴趣调用(Interest Invoker)
一句话的解释
hint暗示:一种更轻量的触发行为的popover类别完整示例
在上小节中,已经讲了popover原有的2个类别,今年它又新增了一个类别:hint 暗示。这种hint弹出层,不仅可以用原来的方法触发它,还增加了一种兴趣调用触发。 兴趣调用Interest Invoker是指:通过非点击事件,比如hover,mouseover,mouseout, focus,blur 它的变化。悬浮就显示,离开就隐藏,十分符合tooltip组件场景。
<buttoninterestfor="mypopover1">悬浮触发 hint1 弹出层</button><divid="mypopover1"popover='hint'>这是一个 hint1 弹出层</div>命令源:它新增以下相关内容:
- 【attr】interestfor: 属性值为 hint类别的popover元素id
- 【css-rule】interest-delay: 设置悬浮触发和离开隐藏的时间。 它是复合属性: interest-delay-start, interest-delay-end。默认触发的时间是 0.5s。
- 【css-selector】 :interest-source 和 :interest-target 是指,如果当前兴趣正在发生,那么触发源和hint 弹出层就分别为具有上面的伪类。类似于 dialog打开时,dialog:open的伪类一样。详见上面示例。
hint 弹出层:新增以下事件:
- 【event】interest: 触发显示的
InterestEvent事件, 事件的source指向触发源元素。 - 【event】loseinterest: 离开失去的
InterestEvent事件,事件的source指向触发源元素。
兴趣调用与前面2节的内容有一些重要的差异:
- 强调必须非点击事件,场景对应“悬而未决”的状态,可以配合popover="hint"使用。
- 触发源更广泛,不仅是Button元素,还允许
<a>, <button>,<area>,SVG <a>。 - hint类别不影响auto类别的弹窗,不会主动触发auto弹窗关闭。
长期以来Web标准对hover行为是淡视的,只有title属性和 :hover的伪类,一直缺少关键的hover事件。此次提供interest事件,借此可以变相的视为一种hover事件
兼容性
支持chrome 142+, 不支持:ff,safari, polyfill 方案。
四、可自定义的select (Customizable select)
一句话的解释
增强的select 和 option 元素,丰富的伪类、伪元素,定制更容易完整示例
可定制的select增加了很多dom规范和伪类,伪元素,内容太多不宜展开细述,感兴趣看上面的完整MDN示例,我已经增加详细的注释。此处仅列出一些重要的概念和事项,以便能快速理解:
- base-select 设置:
select 和 ::picker(select)伪元素都必须添加规则:appearance: base-select,以区别于传统select样式。
- 弹出层::picker(select)特性:
它渲染在页面顶层Top Layer,这意味着它会显示在所有其他内容之上,不会被父容器裁剪。浏览器还会根据视口中的可用空间自动调整下拉列表的位置和翻转。
- 增强的option:
传统的option元素仅支持 label,value属性和selected,disabled的布尔属性。option中嵌套有其它元素,都是会忽略的。
增强后的option元素支持嵌套span,img等等普通元素,但要避免嵌套 a, input 等交互元素就行了。
- 新增 selectedcontent 元素:
该元素必须遵循select > button > selectedcontent的嵌套结构,详见示例。
select的选择值(即change事件)之后,选中的option的节点会被cloneNode创建副本,插入到selectedcontent中,所以他们结构一样,但不是同一个元素实例。
同时button是惰性的,不响应focus等,行为更像是div。
option 和 selectedcontent 的子项,都可以用普通的 css 选择器去分别控制样式。
- select 借用 Popover API
它隐式借用了非常多的Popover 特性,比如 :popover-open伪类,无需anchor-name的隐式的锚点引用,且可以定义弹出层与锚点的位置关系,溢出翻转等等。
- select的multiple 和 optgroup 未增强
这意味着多选和分组功能,需要重新实现,对于组件库的作者来说,这无疑得回退到传统方案,幸好有Popover API。
兼容性:
支持 chrome 135+ , ff,safari均 不支持😭,polyfill 方案
这个方案并非真正意义的polyfill, 它使用自定义的 webComponent技术实现了平替。
五、滚动控制伪元素(::scroll-marker/button())
一句话的解释
为滚动容器的添加伪元素,用于控制容器滚动 完整示例
HTML早早添加了dialog, detail 等元素,但一直没有增加一个轮播图元素,今年只抠抠搜搜添加了三个伪元素,或许是因为添加一个新元素需要考虑的事情太多。
- ::scroll-button()
滚动容器按钮的伪元素,点击它会触发容器滚动。它非常类似于 ::before, ::after作用, 都需要content才显示,且呈现在容器的内部。
每个容器最多有4个滚动方向,括号的作用是指定滚动方向,可取值:*, left,right,up,down, block-end,block-start, inline-end,inline-start等。
按钮伪元素具有状态,比如容器滚动到两端之后,滚动按钮会自动禁用。 它具有以下状态: enabled, disabled,hover,active,focus
- ::scroll-marker 是滚动容器中,指示
滚动项的伪元素,它同样也需要content才显示。
它具有:target-current 伪类, 表示滚动到当前滚动项。当然, :hover, :active等伪类也能使用
- ::scroll-marker-group 是呈现在滚动容器内部的伪元素,收集容纳所有的::scroll-marker元素。
marker-group元素自身没有高度,但可以设置边框,布局,间距等内容。
兼容性:
支持chrome 135+ , ff,safari均 不支持😭。由于它是css 特性,无法Polyfill, 建议使用传统的div去实现即可!
六、设置滚动标记组容器(scroll-target-group)
一句话的解释
设置元素为滚动容器 完整示例
CSS属性 scroll-target-group 用来指定一个元素为滚动标记组容器, 它只有2个值:
- none: 元素非滚动标记组容器
- auto: 元素为滚动标记组容器
滚动标记组容器中通常包含锚点链接列表等,配合伪类 :target-current 来突出显示某个锚点,效果非常类似传统的Anchor组件,当容器滚动时,可以高亮指定的目录项,不过这些都是浏览器自动完成的,不需要一行javascript。
它与::scroll-marker-group 有某些相似点:
- ::scroll-marker-group:是在某个元素内部,创建一个伪元素容器,用来容纳::sroll-marker, 都是伪元素。
- scroll-target-group: 是把一个真实元素变为滚动容器,内部放真实的link 类元素,所以控制上会更灵活。
从官方的态度看,这2个概念极其相近,都是定义了一个滚动容器,且内部的锚点行为一致,均支持伪类 :target-current 代表高亮状态,避免Javascript去滚动和设置高亮等。
兼容性:
支持 chrome 140+ ,但ff,safari均 不支持😭,且css 特性无法Polyfill。
七、锚定容器查询(Anchored Container Queries)
一句话的解释
锚点定位时,翻转状态可以查询完整示例
2024年的CSS回顾中,介绍了CSS锚点定位-—— anchor positioning, 实现类似 Tooltip组件的效果,让一个弹出层锚定到目标元素周围,且能自动翻转到适合位置,避免使用Javascript。 它的兼容性: chrome125+, safari26+, ff 不支持。
下面例子演示了:CSS锚点定位。tooltip会锚定在button的上下, 当滚动到边界时,会自动翻转显示。
.my-button{anchor-name:--my-btn-anchor;/* 定义一个名为 --my-btn-anchor 的锚点 */}.my-tooltip{position:absolute;/* 或 fixed */position-anchor:--my-btn-anchor;/* 关联到上面定义的锚点 */position-area:bottom;position-try-fallbacks:flip-block;}思考一个问题:如果my-tooltip元素有小三角指示方向,那么简单的翻转后,小三角的位置怎么旋转呢?
答案是:定位元素无法意识(be aware)状态,小三角方向会错误。
此问题的解决方案即本次新增了CSS锚定容器查询能力,它通过指定tooltip的container-type: anchored, 然后使用@container anchored的查询语法,就让tooltip查询到,意识到自身的翻转状态(fallbacks 状态)。
.tooltip{container-type:anchored;/* 默认在下方, 小三角向上,位置在底部 */&::before{content:'▲';position:absolute;bottom:100%;}}/** 当容器查询到锚点变化 */@containeranchored(fallback:flip-block){.tooltip::before{/* 小三角向下,并移到到顶部 */content:'▼';bottom:auto;top:100%;}}要讲明白锚点定位需要很大篇幅,且该示例复杂,大家可以转到官网查看示例。 目前container-type: anchored的文档连MDN上都没有,是比较新的概念。
container-type有效值:
- normal: 元素不支持任何查询
- size: 支持 inline 和 block 元素的尺寸的高度和宽度查询
- inline-size: 仅支持 inline 元素的尺寸的宽度查询
- scroll-state: 支持滚动态的偏移量和是否滚动到底等查询,chrome 133+, 但ff,safari均不支持
- anchored: 锚点查询
兼容性:
container-type: anchored支持 chrome 143+ ,但ff,safari均不支持😭,且css 特性无法Polyfill。
总结
通过以上种种新特性,可以看出chrome 不仅关注使用用户体验,更关注开发者体验。通过增加类似command属性,或者Light Dismiss的默认行为,以及滚动容器,滚动容器伪元素等技巧,让许多场景都可以无Javascript实现了。 不仅大大减少开发代码,还有极强的DOM可读性。
我目前从事于组件库开发。在组件库的开发时,所采用的技术通常是落后于浏览器最新技术的,理由就是为了兼容用户浏览器。比如 dialog 元素已经是广泛兼容,但目前仍没有见到哪个组件库使用它。通过这次梳理技术,感觉借助 dialog 以及 popover api 可以极大简化以往的组件开发,诸如:监听按键,计算z-index,计算弹出层位置,监听滚动进行位置跟随等等,这些是问题bug集中爆发区,现在基本都可以无Js代码的实现了。
另外,前面的诸多未广泛兼容的技术,大都有相应的Polyfill,尤其是属性,函数和事件的Polyfill基本都能找到。CSS的新伪类,伪元素虽然很难有Polyfill,但可以用添加类名的方案来兼容,辅助以一些Js事件就可以实现某种程度上的polyfill。oddbird.tech是一家服务公司,得到过Google的赞助,它们一直关注开发Popover API和Anchor positioning的兼容方案。这些方案都让我们以及早的使用新技术进行开发。
如果只需要支持最新的浏览器,前端的春天来了!
关于OpenTiny
欢迎加入 OpenTiny 开源社区。添加微信小助手:opentiny-official 一起参与交流前端技术~
OpenTiny 官网:https://opentiny.design
OpenTiny 代码仓库:https://github.com/opentiny
TinyVue 源码:https://github.com/opentiny/tiny-vue
TinyEngine 源码: https://github.com/opentiny/tiny-engine
欢迎进入代码仓库 Star🌟TinyEngine、TinyVue、TinyNG、TinyCLI、TinyEditor~
如果你也想要共建,可以进入代码仓库,找到 good first issue 标签,一起参与开源贡献~