CSS Feature Query

一.作用

与media query(媒体查询)类似,feature query(特性查询)也是一种条件样式,仅在支持特定样式规则的环境应用指定的一组样式:

The @supports CSS at-rule lets you specify declarations that depend on a browser’s support for one or more specific CSS features. This is called a feature query.

等等,这种能力似乎CSS生来就有

为了保证新属性和新值将来可以添在现有的属性上,用户代理必须忽略一份非法样式表的某一部分,如含有未知属性的声明、含有非法值的声明、含有未知@关键字的@规则等等。

P.S.具体见4.2 处理解析错误的规则

CSS从设计之初就是容错的,当前环境下支持的样式规则会被正确应用,不支持的会被静默忽略掉:

Browsers simply skip over code they don’t understand, without throwing an error.

比如经常见到的:

.card {
  margin: 10px;
  border: 1px solid #ddd;
  box-shadow: 3px 5px 5px #eee;
}

我们知道在支持box-shadow的环境下会呈现阴影,使之看起来像是一张悬浮的卡片,在不支持的环境则只剩下外边距与边框,变成扁平的普通矩形块,算是天然的样式降级。这种包容能力,让新特性的应用少了一些忧虑(大不了不支持,回到降级方案)

那么,feature query带来了什么能力

相当于内置了友好的渐进增强机制,之前通常用Modernizr来做的事情,现在多了一种选择,比如:

Override one layout method with another.

与之前的区别在于,就影响范围而言,容错降级只会影响应用了这些不支持样式的元素,而特性查询降级能够影响一组任意元素,例如:

.card {
  margin: 10px;
  border: 1px solid #ddd;
}
@supports (box-shadow: 3px 5px 5px #eee) {
  /* 影响其它元素,之前依靠CSS容错降级是做不到的 */
  body:before {
    content: 'box-shadow is supported!';
    background-color: green;
  }

  .card {
    box-shadow: 3px 5px 5px #eee
  }
}

P.S.严格地讲,容错降级一般是声明级的(一些声明被忽略或应用),而特性查询降级是规则集级的(一些规则集被忽略或应用):

声明要么为空要么由一个后面跟着冒号(:)和属性值的属性名组成,之间可以有空白字符

规则集(也叫“规则”),由后面跟着一个声明块的选择器组成

二.语法

语法层面,feature query称为@supports CSS at-rule,与media query对比如下:

@supports (display: grid) {
  div {
    display: grid;
  }
}

@media screen and (min-width: 900px) {
  article {
    padding: 1rem 3rem;
  }
}

长得很像,都表示@关键字后面的条件成立时,应用{...}里的样式规则。二者都是条件分组规则(conditional group rules):

/* 一般结构 */
@IDENTIFIER (RULE) {/* CSS block */}

/* @supports CSS at-rule */
@supports <supports-condition> {
  <group-rule-body>
}

另外,条件部分支持与(and)或(or)非(not)逻辑运算:

@supports (display: grid) and (not (display: inline-grid))
@supports (transform-style: preserve) or (-moz-transform-style: preserve)

P.S.仅支持属性名-值对儿形式的条件,不支持其它形式的,比如:

@supports (@charset "utf-8") {
  /* 样式规则 */
}

三.用法

实际场景中,一般模式(最佳实践)为:

/* 降级样式-针对低端环境 */

@supports (前沿特性) {
  /* 增强样式-针对高端环境 */
  /* 必要的话,覆盖某些降级样式 */
}

需要注意的是,不支持某特性并不等价于否定形式的feature query(@supports not):

@supports not (height: 100vh) {
  /* 期望仅在不支持vh的环境应用这组样式规则 */
}

无法如预期地筛选出不支持vh的环境,因为如果连@supports都不支持,整个@规则都会被忽略,包括这组降级样式。也就是说,这个判断不可靠,会漏掉一部分(既不支持@supports也不支持vh的环境)

同样,肯定形式的feature query也不是完全可靠的,例如:

@supports (height: 100vh) {
  /* 期望仅在支持vh的环境应用这组样式规则 */
}

在不支持@supports但支持vh的环境,就不符合预期。但这通常不足为虑,因为一般需要判断支持性的特性比feature query更前沿一些

四.优雅降级与渐进增强

针对浏览器不一致的问题,这是两种类似的应对策略,区别在于:

  • 优雅降级:高端环境优先,妥协让步低端环境(特效全开,低端环境进行特效降级,底线是保证可用)

  • 渐进增强:低端环境优先,特殊照顾高端环境(先保证可用的底线,再考虑加特技)

Graceful degradation starts complex with a goal of providing a simple experience when needed. Progressive enhancements starts simple and then adds on to that with the desired feature-rich experience.

Modernizer

Modernizr,一般特性检测方案,通过JS来检查运行环境是否支持指定特性:

Modernizer checks if a feature is available in the browser and returns true or false.

简言之,Modernizr有助于区分高端与低端环境,这样就允许我们针对低端环境进行降级(fallback),或者打补丁(polyfill)。例如:

if (Modernizr.awesomeNewFeature) {
  showOffAwesomeNewFeature();
} else {
  getTheOldLameExperience();
}

作为一种JS方案,优势是足够灵活,不仅支持查询CSS特性,还支持所有可以通过JS检测的特性,例如:

// 媒体特征
var query = Modernizr.mq('(min-width: 900px)');
if (query) {
  // the browser window is larger than 900px
}
// DOM事件
Modernizr.hasEvent('blur') // true;
// 插件特性
Modernizr.on('flash', function( result ) {
  if (result) {
  // the browser has flash
  } else {
    // the browser does not have flash
  }
});

但存在几个问题:

  • 性能:需要引入额外JS,需检测的新特性会越来越多,体积势必越来越大,存在性能负担

  • 扩展性:依赖第三方支持,最新的特性可能需要等待一段时间才有对应的特性检测,相当于(更新Modernizr版本)手动扩展

  • 易用程度:通过查表得到目标特性的名称(如batteryapiflexbox等,更多名称见Features detected by Modernizr)后,才能检查该特性,不很方便

  • 特性粒度:以上面提到的特性名称为最小检测单元,并不一定合适,比如justify-content: space-evenly可能没有与之对应的特性名称

  • 可靠性:依靠辅助手段来检测特性支持性,并不百分百靠谱,比如部分实现的版本,可能无法准确区分出来

CSS Feature Query

浏览器内置的CSS特性检测支持。是否支持某样式规则,最清楚这件事的当然是浏览器自己,只是这次通过feature query把这种内部状态暴露出来了而已

对比Modernizer,有几个优势:

  • 性能更优:纯CSS方案,不需要JS参与

  • 扩展性良好:作为浏览器的基础能力,任意新特性发布后都立即可检测,不需要手动扩展

  • 语法自然:以样式声明作为查询条件,而不需要查表获得特性名

  • 细粒度:属性名-值对儿粒度,足够灵活

  • 可靠:支持不支持是浏览器自己说的,绝对靠谱

当然,缺点是只支持样式特性查询,对于非CSS特性则无能为力。所以就功能而言,Modernizer是CSS feature query的超集

五.兼容性

  • 桌面:FirefoxChrome[Safari 9+][Edge 12+][IE 11-]都不支持)

  • 移动:[iOS 9.0+][Android 4.4+]

P.S.具体见Can I use

移动端基本可以放心使用了,即便不支持@supports,也没有实质影响(只是会忽略这组样式,与低端环境的表现一致)

特殊的,需要注意几点:

  • feature query无助于识别存在bug的特性实现,与某些不完整的特性实现(比如不支持某种机制,但无法从属性名/值上区分出来)

  • feature query特性自身的兼容性问题会导致某些场景不符合预期(比如支持某特性,却由于不支持@supports而被忽略掉了),但不会造成严重影响

一个典型的例子是Safari 8支持flexbox,但不支持feature query,就会出现bad case:

Safari 8 is likely the biggest problem when it comes to Feature Queries, not Internet Explorer. There are many newer properties that Safari 8 does support — like Flexbox. You probably don’t want to block Safari 8 from these properties.

例如:

body {
  background-color: red;
}

@supports (display: flex) {
  body {
    display: flex;
    background-color: green;
  }
}

Safari 8下就呈现降级样式了,虽然它支持flexbox

六.应用场景

就应用场景而言,feature query用来解决新特性兼容性方面的忧虑,作为渐进增强的一种手段,一般用法:

/* 兼容性可靠的样式:保证可访问性 */

@supports (/* 兼容性不太可靠的新特性 */) {
  /* 增强:支持的话,用更简单、效率更高、更强大的方案 */
}

渐进增强,意味着要接受多环境下的不一致。实际上,对于阴影、圆角、动画之类的很容易接受这种不一致(在不友好的环境去掉这些锦上添花的效果),而对于flexbox、grid等布局方案,似乎很难与渐进增强联系起来,因为布局通常是不可或缺的,而不只是锦上添花

渐进使用grid特性

Is There A CSS Grid Polyfill?

Grid does things that are pretty much impossible with older layout methods. So, in order to replicate Grid in browsers that don’t have support, you would need to do a lot of work in JavaScript.

尽管很遗憾,但grid布局确实没有CSS polyfill。那么,非得等到多年以后才能使用这个强大的特性吗?

当然不是。至少有两种选择

  • 使用JS polyfill,比如FremyCompany/css-grid-polyfill

  • 渐进地(仅在支持的环境)使用grid特性(即接受不同环境下的布局存在差异)

JS补丁方案没什么好说的,对于渐进方案,关键在于接受这种差异

Websites do NOT need to look the same on every browser.

把布局效果也当做一种增强样式(像阴影、圆角等效果一样),允许在低端环境展示另一种不同的降级(布局)效果。例如:

<div class="grid">
  <div class="one">One</div>
  <div class="two">Two</div>
  <div class="three">Three</div>
</div>

对应样式为:

* { box-sizing: border-box; }

.grid {
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-auto-rows: 100px;
  grid-gap: 20px;
}

.grid > * {
  padding: 10px;
  border: 5px solid rgba(214,129,137,.5);
  border-radius: 5px;
  background-color: rgba(233,78,119,.5);
  color: #fff;
  float: left;
  width: 33%;
}

@supports (display:grid) {
  .grid > * {
    width: auto;
  }
}

(摘自display: feature queries demo

在支持Grid的环境下,呈现为漂亮的泾渭分明的3列等比布局,在不支持的环境,降级到稍显拥挤的float布局:

grid-layout-css-polyfill

grid-layout-css-polyfill

差强人意,但在不改变结构、不借助JS的前提下,float方案差不多只能做到这种程度了。如果能够接受类似差异的话,通过feature query渐进使用,新特性的兼容问题将不再重要

检查是否支持自定义属性

@supports (--foo: green) {
  :root {
    --theme-color: gray;
  }

  .variable {
    color: var(--theme-color);
  }
}

利用CSS变量,很容易把换肤功能作为一种增强效果来提供

首字母下沉效果

在支持initial-letter的环境(如Safari)很容易实现这种常见的排版效果(段落首字母下沉4行):

@supports (initial-letter: 4) or (-webkit-initial-letter: 4) {
  p::first-letter {
    -webkit-initial-letter: 4;
    initial-letter: 4;
    color: #FE742F;
    font-weight: bold;
    margin-right: .5em;
  }
}

不支持的话,用常规方式来实现:

p::first-letter {
  float: left;
  font-size: 4em;
  color: #FE742F;
  font-weight: bold;
  margin-right: .5em;
}

P.S.更多案例见参考资料中feature query相关的,如mix-blend-mode

参考资料

发表评论

电子邮件地址不会被公开。 必填项已用*标注

*

code