2024年CSS规则井喷,咋用@supports检测新特性?

3,643字
15–23 分钟
in

写代码时总碰上一些摸不着头脑的CSS新规则?2024年一口气冒出7个@规则,比过去十年加起来还多。本文扒一扒这些新玩意的来头,顺便手把手教大伙用@supports和JavaScript精准检测浏览器是否支持某个at-rule,避免样式翻车。

目录

啥是CSS at-rule

CSS里的at-rule就是以@符号打头的指令,比如@media@import@keyframes。它们就像给浏览器下的“特别命令”,告诉浏览器在特定条件下执行某套样式。2024年之前,主流浏览器支持的at-rule总共也就11个左右,结果今年直接炸出7个新成员——@scope@starting-style@property这些狠角色全来了。

@property举个例子,它用来定义自定义CSS变量(也叫CSS Houdini变量),能让变量拥有类型、初始值、甚至支持动画。要是浏览器不认识这货,整个自定义变量就会失灵。所以检测at-rule的支持情况就成了刚需。

历史快照:为啥以前没人管

1998年CSS 2标准发布时,只有@import@page两个at-rule。到2011年CSS 2.1才加了个@media。中间虽然冒出了@font-face@keyframes,但都是各自模块单飞,没有一个统一的大版本。那会儿大伙写样式基本不用操心兼容性,因为at-rule太少,浏览器支持也早烂大街了。

2011年@supports规则诞生,它的设计初衷是检测CSS属性和值,比如display: grid浏览器认不认。压根没人想到要去检测at-rule,因为当年根本就没几个at-rule需要测。直到2024年,事情变了——Chrome团队突然把at-rule()函数的状态从“New”改成了“Assigned”,说明官方也意识到这个需求迫在眉睫。

手工检测流程

下面直接上硬核操作,分两套方案:一套纯CSS(但有局限),另一套JS兜底。

方案A:用@supports测属性值

@supports虽然不能直接测at-rule,但可以曲线救国。很多新at-rule都会伴随一个专属CSS属性,比如@property一般会配合--*自定义属性使用。写一个检测块:

@supports (--my-var: initial) {
  /* 如果浏览器认识自定义属性,说明@property大概率也支持 */
  .box { background: green; }
}

注意,这种办法只能算“侧面敲击”,不保真。比如@starting-style没有绑定特定属性,这招就废了。实际操作中需要先确认目标at-rule是否有关联属性——查MDN或者caniuse的数据比较靠谱。

方案B:JS动态注入+错误捕获

更精准的姿势是用JavaScript创建样式表,尝试插入at-rule,然后监听有没有报错。写一个检测函数:

function isAtRuleSupported(ruleText) {
  const style = document.createElement('style');
  style.textContent = ruleText;
  try {
    document.head.appendChild(style);
    // 插入成功说明浏览器解析了这个规则
    const isSupported = style.sheet.cssRules.length > 0;
    style.remove();
    return isSupported;
  } catch (e) {
    // 抛异常说明不认得这条规则
    style.remove();
    return false;
  }
}

// 检测@scope
console.log(isAtRuleSupported('@scope (.a) to (.b) { .c { color: red; } }'));
// 检测@starting-style
console.log(isAtRuleSupported('@starting-style { .box { opacity: 0; } }'));

这段代码的核心逻辑是:把at-rule塞进<style>标签,浏览器能解析就说明支持,解析不了就报错。有个坑必须留意——某些at-rule有语法限制,比如@import必须放在样式表最顶部,单独扔进style标签可能会被浏览器无视而不是报错。针对@import,可以用CSS.supports() API单独处理:

// 更干净的写法,适用于CSS.supports()能认的at-rule
console.log(CSS.supports('@import url("a.css")')); // 返回true或false

实测发现CSS.supports()@import@namespace支持良好,但对@scope这类新规则可能返回false误报。所以最稳的组合拳是:先试CSS.supports(),如果返回false再跑一遍try-catch注入法。

分步实战:检测@scope

@scope是2024年的重磅新规,用来限定选择器的作用范围。假设要在一个项目里使用它,得先确认浏览器环境是否支持。

第一步,打开浏览器控制台(F12),切到Console面板。

第二步,输入以下检测代码

function testScope() {
  const style = document.createElement('style');
  style.textContent = '@scope (.container) { .inner { color: red; } }';
  document.head.appendChild(style);
  const hasRule = style.sheet.cssRules.length > 0;
  style.remove();
  return hasRule;
}
console.log(testScope() ? '支持@scope' : '不支持@scope');

如果返回true,说明当前浏览器(比如Chrome 118+)认得@scope。返回false就要准备降级方案。

第三步,写降级样式。利用@supports配合一个浏览器不认识的属性值来兜底:

/* 高级样式 */
@scope (.card) to (.card-footer) {
  .title { font-size: 2rem; }
}

/* 降级方案:老浏览器直接全局样式 */
.title { font-size: 1.5rem; }

注意,降级样式必须写在前面,防止覆盖。或者用@supports not来包裹老样式:

@supports not (selector(:scope)) {
  /* 连:scope伪类都不认的浏览器,直接全局伺候 */
  .title { font-size: 1.5rem; }
}

这里有个隐藏雷区:@supports本身也有兼容性问题(IE完全无视),但IE已经退环境了,现代浏览器基本都支持。

表格速查:主流浏览器at-rule支持时间

规则名首发年份ChromeFirefoxSafari
@import19981+1+1+
@media20111+1+1+
@supports201328+22+9+
@property202485+128+17.4+
@scope2024118+128+17.4+

数据来自caniuse整理,注意Firefox 128是2024年7月发布的,Safari 17.4也是今年春季更新。这说明2024年确实是at-rule的“大年”。

备胎方案:polyfill垫片库

如果项目必须兼容老掉牙的浏览器(比如Chrome 80以下),可以考虑引入polyfill。搜一下“css-scope-polyfill”或者“css-supports-polyfill”,这些库能在JS层面模拟部分at-rule的行为。不过要注意,polyfill往往有性能损耗,而且对语法糖类规则(比如@scope)模拟不完全。最佳实践是只在确认需要降级的环境才动态加载polyfill:

if (!isAtRuleSupported('@scope (.a) {}')) {
  const script = document.createElement('script');
  script.src = 'https://polyfill.example/scope.js';
  document.head.appendChild(script);
}

实际生产环境建议把polyfill文件放到自己的服务器上,避免引用外部不可控资源。另外,2024年出的at-rule大多数只在最新浏览器里能用,项目里使用前最好先统计一下用户浏览器版本分布——别为了炫技把线上页面搞崩了。

结尾彩蛋:未来会有at-rule()函数

Chrome团队已经把at-rule()函数的状态从“New”改成“Assigned”,意味着不久的将来可以直接用@supports at-rule(@scope)这种写法来检测。到时候上面那些JS骚操作就可以扔进历史博物馆了。不过在那之前,该写的检测代码还是得老老实实写。