Sass 中的媒体查询方法

Avatar of Eduardo Bouças
Eduardo Bouças

DigitalOcean 提供适合您旅程各个阶段的云产品。 立即开始使用 200 美元的免费积分!

在响应式网站中使用 CSS 中的媒体查询是当今前端开发人员的必备技能。 使用预处理器使它们更易于编写和维护也已成为一种常见的做法。

我花了几月时间尝试了十多种不同的 Sass 媒体查询方法,并在生产中实际使用了几种。 他们最终都无法以优雅的方式满足我需要完成的所有工作。 因此,我从每种方法中汲取精华,创建了一个可以涵盖我遇到的所有场景的解决方案。

为什么使用预处理器?

这是一个合理的问题。 毕竟,如果可以简单地使用纯 CSS 编写媒体查询,为什么要做这些呢? 整洁和可维护性。

媒体查询最常见的用途是根据浏览器的视窗宽度转换布局。 可以使布局适应,以便不同屏幕尺寸的多个设备都能获得最佳体验。 因此,用于定义媒体查询的表达式将引用这些设备的典型屏幕宽度。

因此,如果代码包含 5 个针对宽度为 768px 的平板设备的媒体查询,则将硬编码该数字 5 次,这很丑陋,我的强迫症永远不会原谅它。 首先,我希望我的代码易于阅读,任何人只需看一眼就能立即理解媒体查询是针对平板设备的 - 我认为 平板电脑 这个词比 768px 更能做到这一点。

另外,如果将来该参考宽度发生变化怎么办? 我讨厌在代码中的 5 个地方替换它,尤其是在它分散在多个文件中的情况下。

第一步是将该断点存储在一个变量中,并使用它来构建媒体查询。

/* Using plain CSS */
@media (min-width: 768px) {
  
}

/* Using SCSS variables to store breakpoints */
$breakpoint-tablet: 768px;
@media (min-width: $breakpoint-tablet) {
  
}

使用 Sass 等预处理器编写媒体查询的另一个原因是它有时可以为语法提供宝贵的帮助,尤其是在编写包含逻辑 的表达式时(在 CSS 中用逗号表示)。

例如,如果您要 针对视网膜设备,纯 CSS 语法会变得有点冗长

/* Plain CSS */
@media (min-width: 768px) and 
       (-webkit-min-device-pixel-ratio: 2), 
       (min-width: 768px) and 
       (min-resolution: 192dpi) { 
}

/* Using variables? */
@media (min-width: $bp-tablet) and ($retina) { // or #{$retina}

}

看起来不错,但不幸的是,它无法按预期工作。

逻辑问题

由于 CSS “或” 运算符的工作方式,我将无法将 视网膜 条件与其他表达式混合使用,因为 a (b 或 c) 将编译为 (a 或 b) c 而不是 a b 或 a c

$retina: "(-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi)";
 
// This will generate unwanted results!
@media (min-width: 480px) and #{$retina} {
  body {
    background-color: red;
  }
}
/* Not the logic we're looking for */
@media (min-width: 480px) and (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
  body {
    background-color: red;
  }
}

我意识到我需要更强大的东西,比如混合器或函数来解决这个问题。 我尝试了一些解决方案。

Dmitry Sheiko 的方法

我尝试过的一种是 Dmitry Sheiko 的方法,它具有良好的语法,并包含 Chris 的视网膜声明。

// Predefined Break-points
$mediaMaxWidth: 1260px;
$mediaBp1Width: 960px;
$mediaMinWidth: 480px;

@function translate-media-condition($c) {
  $condMap: (
    "screen": "only screen",
    "print": "only print",
    "retina": "(-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3/2), (min-device-pixel-ratio: 1.5), (min-resolution: 120dpi)",
    ">maxWidth": "(min-width: #{$mediaMaxWidth + 1})",
    "<maxWidth": "(max-width: #{$mediaMaxWidth})", 		
    ">bp1Width": "(min-width: #{$mediaBp1Width + 1})",
    "<bp1Width": "(max-width: #{$mediaBp1Width})",
    ">minWidth": "(min-width: #{$mediaMinWidth + 1})",
    "<minWidth": "(max-width: #{$mediaMinWidth})"
  );
  @return map-get( $condMap, $c );
}

// The mdia mixin
@mixin media($args...) {
  $query: "";
  @each $arg in $args {
    $op: "";
    @if ( $query != "" ) {
      $op: " and ";
    }
    $query: $query + $op + translate-media-condition($arg);
  }
  @media #{$query}  { @content; }
}

但逻辑析取问题依然存在。

.section {
  @include media("retina", "<minWidth") {
    color: white;
  };
}
/* Not the logic we're looking for */
@media (-webkit-min-device-pixel-ratio: 1.5), (min--moz-device-pixel-ratio: 1.5), (-o-min-device-pixel-ratio: 3 / 2), (min-device-pixel-ratio: 1.5), (min-resolution: 120dpi) and (max-width: 480px) {
  .section {
    background: blue;
    color: white;
  }
}

Landon Schropp 的方法

Landon Schropp 是我的下一站。 Landon 创建了简单的命名混合器,它们执行特定的任务。 像

$tablet-width: 768px;
$desktop-width: 1024px;

@mixin tablet {
  @media (min-width: #{$tablet-width}) and (max-width: #{$desktop-width - 1px}) {
    @content;
  }
}

@mixin desktop {
  @media (min-width: #{$desktop-width}) {
    @content;
  }
}

他也提供了一个单一职责的视网膜版本。

但是,当我为需要在中间断点上添加额外规则的元素设置样式时,另一个问题出现了。 我不想用特定于用例的值污染我的全局断点列表,只是为了能够使用混合器,但我也不想放弃混合器,回到使用纯 CSS 并每次必须使用自定义值时都进行硬编码。

/* I didn't want to sometimes have this */
@include tablet {

}

/* And other times this */
@media (min-width: 768px) and (max-width: 950px) {

}

断点技术

Breakpoint-sass 是我的下一个选择,因为它支持其语法中的变量和自定义值(另外,它在 像素比率媒体查询 方面非常巧妙)。

我可以写类似的东西

$breakpoint-tablet: 768px;

@include breakpoint(453px $breakpoint-tablet) {

}

@include breakpoint($breakpoint-tablet 850px) {

}

/* Compiles to: */
@media (min-width: 453px) and (max-width: 768px) {

}

@media (min-width: 768px) and (max-width: 850px) {

}

事情看起来好多了,但我个人认为 Breakpoint-sass 的语法感觉不如 Dmitry 的自然。 可以提供一个数字,它假设它是一个 min-width 值,或者一个数字和一个字符串,它假设一个属性/值对,仅举几个它支持的组合。

这很好,我相信一旦习惯了它,它就能很好地工作,但我并没有放弃寻找既简单又尽可能接近我口头描述媒体查询必须针对的内容的语法。

另外,如果看一下上面的示例,您会发现宽度正好为 768px 的设备将触发两个媒体查询,这可能不是我们想要的。 因此,我在我的要求列表中添加了编写包含性和排他性断点的能力。

我的(Eduardo Bouças)方法

这是我针对它的 方法

简洁的语法,动态声明

我是 Dmitry 语法的粉丝,因此我的解决方案受到了它的启发。 但是,我希望在创建断点的方式上拥有更大的灵活性。 我没有在混合器中硬编码断点的名称,而是使用了一个多维映射来声明和标记它们。

$breakpoints: (phone: 640px, 
               tablet: 768px, 
               desktop: 1024px) !default;

@include media(">phone", "<tablet") {
}

@include media(">tablet", "<950px") {
}

混合器带有一组默认断点,可以在代码中的任何位置通过重新声明变量 $breakpoints 来覆盖它们。

包含性和排他性断点

我希望对表达式中的区间有更细粒度的控制,因此我加入了对 小于或等于大于或等于 运算符的支持。 这样,就可以在两个互斥的媒体查询中使用相同的断点声明。

@include media(">=phone", "<tablet") {

}

@include media(">=tablet", "<=950px") {

}

/* Compiles to */
@media (min-width: 640px) and (max-width: 767px) {

}

@media (min-width: 768px) and (max-width: 950px) {

}

推断媒体类型并处理逻辑析取

与断点类似,还有一个列表用于媒体类型和其他默认声明的静态表达式(可以通过设置变量 $media-expressions 来覆盖它)。 这添加了对可选媒体类型(例如 screenhandheld)的支持,但它还能够正确处理包含逻辑析取的表达式,例如我们之前看到的视网膜媒体查询。 析取声明为字符串的嵌套列表。

$media-expressions: (screen: "screen", 
                    handheld: "handheld",
                    retina2x: 
                    ("(-webkit-min-device-pixel-ratio: 2)", 
                    "(min-resolution: 192dpi)")) !default;

@include media("screen", ">=tablet") {

}

@include media(">tablet", "<=desktop", "retina2x") {

}

/* Compiles to */
@media screen and (min-width: 768px) {

}

@media (min-width: 769px) and 
       (max-width: 1024px) and 
       (-webkit-min-device-pixel-ratio: 2),
       (min-width: 769px) and 
       (max-width: 1024px) and 
       (min-resolution: 192dpi) {

}

这里没有火箭科学,但是混合器的完整实现无法用几行代码展示出来。 我没有用巨大的代码片段和无休止的注释来烦你,而是包含了一个包含所有工作内容的 Pen,我将简要介绍它构建媒体查询的过程。

工作原理

  1. Mixin 接收多个参数作为字符串,并首先遍历每个参数,以确定它是否代表断点、自定义宽度或静态媒体表达式之一。
  2. 如果找到运算符,则提取该运算符,并返回任何匹配的断点,否则假设它是一个自定义值,并将其转换为数字(使用 SassyCast)。
  3. 如果是静态媒体表达式,它会检查任何 or 运算符并生成表示析取所需的所有组合。
  4. 对所有参数重复此过程,并将结果通过 and 连接符粘合在一起,形成媒体查询表达式。

如果您想查看完整的 Sass 代码,请点击这里。它在 GitHub 上被称为 include-media

最后的想法

  • 我非常喜欢 这种技术,它可以使 Sass 与 JavaScript 互相通信。因为我们将断点声明为具有名称作为键的多维列表,所以将它们批量导出到 JavaScript 变得非常简单,只需几行代码就可以自动完成。
  • 我并不是要贬低其他人的解决方案,当然我也不会说我的解决方案更好。我提到它们是为了展示我在寻找理想解决方案的过程中遇到的障碍,以及一些它们引入的伟大功能,这些功能启发了我的解决方案。
  • 您可能对实现的长度和复杂性有一些顾虑。虽然我理解这一点,但它背后的理念是,您可以下载单个文件,将其 @import 到您的项目中,并开始使用它,而无需触及源代码。如果您有任何问题,请在 Twitter 上联系我。
  • 您可以在 GitHub 上获得它,并且非常欢迎您通过问题/代码/爱来做出贡献。我相信我们还有很多工作要做,才能让它变得更好。

更新!

Eduardo 为他的方法创建了一个网站:@include-media