Skip to content

CSS at-Rule @supports 和 `CSS.supports()` API

约 1680 字大约 6 分钟

2021-08-21

在现代前端开发中,浏览器兼容性是一个永恒的话题。随着 CSS 标准的快速发展, 如何优雅地检测浏览器是否支持某个 CSS 特性变得尤为重要。本文将深入探讨两种强大的特性检测工具: JavaScript 的 CSS.supports() API 和 CSS 的 @supports at-rule。

引言:为什么需要特性检测

特性检测的重要性

在过去,开发者通常使用用户代理嗅探(User Agent Sniffing)来判断浏览器能力,但这种方法既不可靠又难以维护。特性检测提供了更准确、更面向未来的解决方案。

随着 CSS 新特性的不断涌现,如 Grid、Flexbox、CSS Variables 等,确保网站在不同浏览器中都能提供良好的用户体验变得至关重要。

CSS.supports() JavaScript API

CSS.supports() 是 CSS 对象模型(CSSOM)的一部分,允许开发者在 JavaScript 中检测浏览器是否支持特定的 CSS 声明或规则。

基本语法

// 检测属性-值对
CSS.supports(propertyName, value)

// 检测条件字符串
CSS.supports(condition)

使用示例

检测属性-值对
// 检测浏览器是否支持 CSS Grid
if (CSS.supports('display', 'grid')) {
  console.log('CSS Grid 支持!')
  // 应用 Grid 布局
  document.body.style.display = 'grid'
}
else {
  console.log('CSS Grid 不支持,使用备用布局')
  // 使用传统布局
  document.body.style.display = 'flex'
}

实际应用:渐进增强

渐进增强示例
function applyModernStyles() {
  const container = document.getElementById('main-container')

  // 检测多个现代特性
  const supportsGrid = CSS.supports('display', 'grid')
  const supportsVariables = CSS.supports('--primary-color: #000')
  const supportsBackdrop = CSS.supports('backdrop-filter', 'blur(10px)')

  if (supportsGrid && supportsVariables) {
    container.classList.add('modern-layout')
    console.log('应用现代样式')
  }
  else {
    container.classList.add('fallback-layout')
    console.log('使用回退样式')
  }
}

CSS @supports at-rule

@supports at-rule 允许在 CSS 中直接进行特性检测,类似于媒体查询,但针对的是 CSS 特性支持。

基本语法

@supports (condition) {
  /* 当条件满足时应用的 CSS 规则 */
}

@supports not (condition) {
  /* 当条件不满足时应用的 CSS 规则 */
}

@supports (condition1) and (condition2) {
  /* 当所有条件都满足时应用的 CSS 规则 */
}

@supports (condition1) or (condition2) {
  /* 当任一条件满足时应用的 CSS 规则 */
}

使用示例

基础检测
/* 检测 Grid 支持 */
@supports (display: grid) {
    .container {
        display: grid;
        grid-template-columns: 1fr 1fr;
        gap: 20px;
    }
}

/* 检测不支持的情况 */
@supports not (display: grid) {
    .container {
        display: flex;
        flex-wrap: wrap;
    }

    .item {
        flex: 1 1 300px;
        margin: 10px;
    }
}

实际应用场景

1. 布局系统的渐进增强

响应式布局系统
/* 基础移动端布局 */
.page-layout {
    display: block;
}

/* 中等屏幕 - Flexbox */
@supports (display: flex) and (max-width: 768px) {
    .page-layout {
        display: flex;
        flex-direction: column;
    }
}

/* 大屏幕 - Grid */
@supports (display: grid) and (min-width: 1024px) {
    .page-layout {
        display: grid;
        grid-template-areas:
            "header header"
            "sidebar content"
            "footer footer";
        grid-template-columns: 250px 1fr;
    }
}

2. 新特性的优雅降级

毛玻璃效果实现
.card {
    background: white;
    border-radius: 8px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
}

@supports (backdrop-filter: blur(10px)) or
          (-webkit-backdrop-filter: blur(10px)) {
    .glass-card {
        background: rgba(255, 255, 255, 0.8);
        backdrop-filter: blur(10px);
        -webkit-backdrop-filter: blur(10px);
    }
}

@supports not ((backdrop-filter: blur(10px)) or
               (-webkit-backdrop-filter: blur(10px))) {
    .glass-card {
        background: rgba(255, 255, 255, 0.95);
    }
}

3. JavaScript 中的动态特性检测

动态加载 polyfill
class FeatureDetector {
  static checkFeatures() {
    const features = {
      cssGrid: CSS.supports('display', 'grid'),
      cssVariables: CSS.supports('--test: value'),
      backdropFilter: CSS.supports('backdrop-filter', 'blur(10px)')
        || CSS.supports('-webkit-backdrop-filter', 'blur(10px)'),
      aspectRatio: CSS.supports('aspect-ratio', '16 / 9')
    }

    return features
  }

  static loadPolyfills() {
    const features = this.checkFeatures()

    if (!features.cssGrid) {
      // 动态加载 Grid polyfill
      this.loadScript('css-grid-polyfill.js')
    }

    if (!features.cssVariables) {
      // 应用 CSS 变量回退方案
      this.applyVariableFallbacks()
    }

    // 更新 HTML 类以便 CSS 使用
    this.updateRootClasses(features)
  }

  static updateRootClasses(features) {
    const root = document.documentElement

    Object.entries(features).forEach(([feature, supported]) => {
      const className = supported
        ? `has-${feature}`
        : `no-${feature}`
      root.classList.add(className)
    })
  }
}

// 页面加载时检测
document.addEventListener('DOMContentLoaded', () => {
  FeatureDetector.loadPolyfills()
})

最佳实践与注意事项

浏览器兼容性

虽然 @supportsCSS.supports() 在现代浏览器中得到良好支持,但在 IE 和旧版浏览器中不可用。始终提供合理的回退方案

1. 性能考虑

// 好的做法:缓存检测结果
class CSSSupportCache {
  constructor() {
    this.cache = new Map()
  }

  check(property, value = null) {
    const key = value ? `${property}:${value}` : property

    if (!this.cache.has(key)) {
      const result = value
        ? CSS.supports(property, value)
        : CSS.supports(property)
      this.cache.set(key, result)
    }

    return this.cache.get(key)
  }
}

const supportCache = new CSSSupportCache()

// 重复使用缓存结果
if (supportCache.check('display', 'grid')) {
  // 应用 Grid 样式
}

2. 渐进增强策略

  • 定义核心体验 首先确保在不支持任何现代特性的浏览器中,网站的基本功能仍然可用

  • 检测特性支持 使用 @supportsCSS.supports() 检测可用特性

  • 分层增强 根据支持的特性逐步添加更高级的样式和交互

  • 提供回退 为每个现代特性都准备合适的回退方案

3. 测试策略

自动化特性检测测试
describe('CSS 特性检测', () => {
  beforeEach(() => {
    // 重置缓存
    window.CSS._supportCache = null
  })

  test('应该检测 Grid 支持', () => {
    // 模拟不同环境
    const originalSupports = CSS.supports

    // 测试支持的情况
    CSS.supports = jest.fn().mockReturnValue(true)
    expect(CSS.supports('display', 'grid')).toBe(true)

    // 测试不支持的情况
    CSS.supports = jest.fn().mockReturnValue(false)
    expect(CSS.supports('display', 'grid')).toBe(false)

    CSS.supports = originalSupports
  })
})

总结

CSS.supports() JavaScript API 和 CSS @supports at-rule 为前端开发者提供了强大的特性检测能力,使得实现渐进增强和优雅降级变得更加简单可靠。

关键要点

重要

  • 使用场景CSS.supports() 适合动态检测,@supports 适合静态样式检测
  • 渐进增强:始终从基础体验开始,逐步添加增强功能
  • 性能优化:缓存检测结果,避免重复检测
  • 回退方案:为每个现代特性准备合适的回退方案
  • 测试覆盖:确保在不同浏览器环境下的兼容性

参考

MDN - CSS.supports()MDN - @supportsCan I use - CSS Feature Queries

通过合理运用这些特性检测工具,你可以构建出既现代化又具有良好兼容性的 Web 应用,为用户提供始终如一的优秀体验。