用 Sass 简化媒体查询:告别重复的 @media 代码

2024-07-02 前端

响应式设计是现代前端的标配,但原生 CSS 的媒体查询写法很容易让代码变得冗长且难以维护——同一个断点值在数十处重复,修改时苦不堪言。Sass/SCSS 的 mixin 机制可以优雅地解决这个问题。本文将带你从零构建一套可复用的响应式断点系统。

一、原生 CSS 媒体查询的痛点

/* 痛点一:断点值散落各处,修改困难 */
.header {
  padding: 16px;
}

@media (min-width: 768px) {
  .header {
    padding: 24px;
  }
}

.card {
  grid-template-columns: 1fr;
}

@media (min-width: 768px) {
  .card {
    grid-template-columns: 1fr 1fr;
  }
}

/* 768px 出现了无数次,如果要改成 800px,全项目搜索替换 */

核心问题:断点值硬编码、代码重复、嵌套层级混乱、视觉上不直观。

二、Sass Mixin 封装断点

基础版 —— 一个断点一个 Mixin

进阶版 —— 统一 Mixin + 参数化

高级版 —— 双向断点 + 区间查询

三、Mixin 嵌套与组合

Sass mixin 可以嵌套使用,组合出复杂而清晰的响应式逻辑:

.product-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 12px;
  padding: 16px;

  @include up('sm') {
    grid-template-columns: repeat(2, 1fr);
    gap: 16px;
    padding: 20px;
  }

  @include up('md') {
    grid-template-columns: repeat(3, 1fr);
    gap: 20px;
    padding: 24px;
  }

  @include up('lg') {
    grid-template-columns: repeat(4, 1fr);
    gap: 24px;
    padding: 32px;
  }
}

// 编译后的 CSS —— 每个断点的样式内聚在组件定义中

与原生写法对比:

// ❌ 原生:样式与断点分离,定位困难
.product-grid {
  grid-template-columns: 1fr;
}
@media (min-width: 640px) {
  .product-grid { grid-template-columns: repeat(2, 1fr); }
}
@media (min-width: 768px) {
  .product-grid { grid-template-columns: repeat(3, 1fr); }
}

// ✅ Sass:样式内聚,一目了然
.product-grid {
  grid-template-columns: 1fr;
  @include up('sm') { grid-template-columns: repeat(2, 1fr); }
  @include up('md') { grid-template-columns: repeat(3, 1fr); }
}

四、常见断点推荐

根据主流设备和行业实践,推荐以下断点体系:

断点名称 宽度 覆盖设备 说明
xs 0 - 639px 手机竖屏 默认基线,不需 min-width
sm 640px+ 手机横屏 / 大屏手机 双列布局起点
md 768px+ 平板竖屏 导航展开
lg 1024px+ 平板横屏 / 小笔记本 侧边栏出现
xl 1280px+ 桌面显示器 多列布局
2xl 1536px+ 大屏 / 宽屏 内容居中+最大宽度
// 实际项目中的断点定义
$breakpoints: (
  'sm':  640px,
  'md':  768px,
  'lg':  1024px,
  'xl':  1280px,
  '2xl': 1536px,
);

// 可选:容器最大宽度
$container-max-widths: (
  'sm':  640px,
  'md':  768px,
  'lg':  1024px,
  'xl':  1280px,
  '2xl': 1536px,
);

五、进阶技巧

1. 暗黑模式 + 响应式组合

@mixin dark {
  @media (prefers-color-scheme: dark) {
    @content;
  }
}

.card {
  background: #fff;

  @include dark {
    background: #1a1a2e;
  }

  @include up('md') {
    @include dark {
      background: #16213e;  // 暗黑模式下的大屏专属色
    }
  }
}

2. 打印样式

@mixin print {
  @media print {
    @content;
  }
}

.article {
  @include print {
    font-size: 12pt;
    color: black;
  }
}

3. 减少运动偏好

@mixin reduced-motion {
  @media (prefers-reduced-motion: reduce) {
    @content;
  }
}

.animated {
  transition: transform 0.3s ease;

  @include reduced-motion {
    transition: none;
  }
}

六、Sass Mixin vs 原生 CSS 自定义属性

特性 Sass Mixin CSS 自定义属性
断点值集中管理 ✅ 天生支持 ⚠️ 需要配合 @media
嵌套语法 ✅ 直观 ❌ 无法嵌套
编译后体积 无额外开销 无额外开销
运行时修改 ❌ 编译时确定 ❌ 自定义属性在 @media 条件中不可用
学习成本 需要 Sass ✅ 纯 CSS
适用项目 使用 Sass/SCSS 的项目 纯 CSS 项目

结论:CSS 自定义属性无法在媒体查询的条件中使用(如 @media (min-width: var(--bp)) 无效),因此 Sass Mixin 仍是最佳实践。

七、完整样式架构示例

八、总结

Sass Mixin 封装媒体查询的核心价值:

  1. 单一数据源:断点值只定义一次,全局修改
  2. 语义化命名:@include up('md') 比 @media (min-width: 768px) 更清晰
  3. 样式内聚:组件所有响应式变体在同一代码块中
  4. 零运行时开销:编译为普通 CSS,不增加体积
  5. 可组合:轻松组合暗黑模式、打印样式等媒体查询

如果你的项目已使用 Sass/SCSS,强烈建议采用这套方案来管理媒体查询。代码量不大,但长期收益巨大。