在响应式网站中使用 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
来覆盖它)。 这添加了对可选媒体类型(例如 screen 或 handheld)的支持,但它还能够正确处理包含逻辑析取的表达式,例如我们之前看到的视网膜媒体查询。 析取声明为字符串的嵌套列表。
$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,我将简要介绍它构建媒体查询的过程。
工作原理
- Mixin 接收多个参数作为字符串,并首先遍历每个参数,以确定它是否代表断点、自定义宽度或静态媒体表达式之一。
- 如果找到运算符,则提取该运算符,并返回任何匹配的断点,否则假设它是一个自定义值,并将其转换为数字(使用 SassyCast)。
- 如果是静态媒体表达式,它会检查任何
or
运算符并生成表示析取所需的所有组合。 - 对所有参数重复此过程,并将结果通过
and
连接符粘合在一起,形成媒体查询表达式。
如果您想查看完整的 Sass 代码,请点击这里。它在 GitHub 上被称为 include-media。
最后的想法
- 我非常喜欢 这种技术,它可以使 Sass 与 JavaScript 互相通信。因为我们将断点声明为具有名称作为键的多维列表,所以将它们批量导出到 JavaScript 变得非常简单,只需几行代码就可以自动完成。
- 我并不是要贬低其他人的解决方案,当然我也不会说我的解决方案更好。我提到它们是为了展示我在寻找理想解决方案的过程中遇到的障碍,以及一些它们引入的伟大功能,这些功能启发了我的解决方案。
- 您可能对实现的长度和复杂性有一些顾虑。虽然我理解这一点,但它背后的理念是,您可以下载单个文件,将其
@import
到您的项目中,并开始使用它,而无需触及源代码。如果您有任何问题,请在 Twitter 上联系我。 - 您可以在 GitHub 上获得它,并且非常欢迎您通过问题/代码/爱来做出贡献。我相信我们还有很多工作要做,才能让它变得更好。
更新!
Eduardo 为他的方法创建了一个网站:@include-media。
这是一个强大的 mixin。做得好。
可能值得写一两篇帖子来教人们如何像这个 mixin 一样全面地思考 SASS/SCSS mixin。我在这篇文章中最喜欢的部分是阅读这个 mixin。
对媒体查询语法来说也是一个很棒的解决方案!总体来说,很棒的东西。
这与 Bootstrap 的简单方法有何不同
…/* 特小型设备(手机,小于 768px) /
/ Bootstrap 的默认设置,没有媒体查询 */
/* 小型设备(平板电脑,768px 及以上) */
@media (min-width: @screen-sm-min) { … }
/* 中型设备(台式机,992px 及以上) */
@media (min-width: @screen-md-min) { … }
/* 大型设备(大型台式机,1200px 及以上) */
@media (min-width: @screen-lg-min) { … }…
真正的前端开发人员通常不想一直依赖像 Bootstrap 这样的框架。它会导致加载不必要的样式表。这样做是为了从样式表中创建更好的媒体查询,而不是从框架中的一堆规则中获取一些现成的类。
这就是我做事的方式。您不必加载像 BS 这样庞大的框架。从框架中提取有用的部分并将其放入自己的代码中并没有什么错。
我也对 Grid 做了同样的事情。您可以使用 @if 和创建选项文件来减少 Grid 的冗余代码,在选项文件中,您可以轻松地添加或删除 Grid 的部分。
@wiziwiz,因为它没有包含您可能不会使用的一百万个不同东西。
通常,当我使用 Bootstrap 或 Foundation 之类的东西时,是为了快速原型或点击操作(演示用户故事或其他什么)。
然后我构建自己的(移动优先)样式表。 Susy 或 zen grids 是很棒的工具,使像 Bootstrap 和 Foundation 这样的框架变得不再需要。
基于 em 的 MQ 现在已经过时了吗?
px 是媒体查询的更好方法,而 em 更适合于排版和块级/内联元素。仅供参考。
我更喜欢基于 em 的媒体查询,因为如果用户决定放大,浏览器会拾取媒体查询并看到响应式网站,因此网站永远不会崩溃。
此外,一些设备报告的字体大小高于 16px,如果是这样,它们也会看到响应式网站,因为它是基于 em 的 MQ。
http://bradfrost.com/blog/post/7-habits-of-highly-effective-media-queries/#relative
http://blog.cloudfour.com/the-ems-have-it-proportional-media-queries-ftw/
有些人甚至建议使用 REM
但是,https://twitter.com/brad_frost/status/335259353466691584
基于 px 的媒体查询的缩放问题是 Chrome 中的一个问题,很久以前就解决了。而且由于旧版本的 Chrome 不会保留,我不太担心它。有趣的是,像这样的想法会留在我们脑海中。
我同意 Tony 的观点。EMs/REMs 适用于排版元素或包装器,或者那些您希望与排版相关的元素。影响结构的元素应该使用 % 或 px。到目前为止,这种哲学对我很有帮助。
Em 不够精确,我遇到过断点之间存在 1px 间隙的问题(如果您定义的是范围断点而不是重叠断点)。Px 是最小的单位,它是精确的。
@Ricardo 它是一个条件语句
@mike mai 这正是我使用 em 设置断点时遇到的问题,我不得不调整和调整才能使断点触发,数学在基本字体大小为 16px 或任何您设置的大小的情况下不起作用,公式不会按预期工作。(抱歉,我不准备举例子,自己试试吧)
@rxrrctct 当然不是。我不确定我们现在是否应该使用 px。
@Tony 我不同意你关于 px 媒体查询的观点。使用基于 em 的媒体查询可以帮助提高网站的可访问性。强烈建议您阅读这篇博文——它可能会彻底改变您的想法。
http://blog.cloudfour.com/the-ems-have-it-proportional-media-queries-ftw/
@Alistar,我喜欢使用 ems,我使用 ems 和 rems。
我发现很有趣的是,您链接的那篇文章的作者指出
我在我的 ScSS 文件中使用 em 和断点时,遇到了几个意外结果。
不确定我是否准备好抽象 CSS 的优雅。
这是一种奇怪的维护媒体查询的方式。我敢打赌,在这种情况下,普通的 CSS 语法更好。
巨大的工作量,显而易见。不过,我想问一个问题,为什么不利用 Sass 组合媒体查询的事实,而是将所有逻辑和(解)构到 SassScript 中?
使用 Landon Schropp 的技术,你可以得到类似这样的东西(虽然我不喜欢每个断点都使用一个 mixin,因为它绝对不可扩展)
这种语法不仅清楚地定义了它将组件样式设置为“平板电脑”断点的意图,而且同时还添加了额外的要求。
你可能想要尝试的事情
– 来自 @Kaelig 的 Sass-MQ,在《卫报》和《金融时报》(以及其他媒体)中使用:https://github.com/sass-mq/sass-mq
– 来自 @lolmaus 的 Breakpoint-slicer,它是 Breakpoint 的语法糖:https://github.com/lolmaus/breakpoint-slicer
对于对这个主题感兴趣的人,我在 SitePoint 上写了一篇关于响应式断点管理的文章:http://www.sitepoint.com/managing-responsive-breakpoints-sass/。希望能帮到你。
我喜欢你的解决方案如何与 SCSS 一起使用
令人印象深刻的工作!
但是内容定义断点是怎么回事呢?
http://bradfrost.com/blog/post/7-habits-of-highly-effective-media-queries/#content
http://www.smashingmagazine.com/2013/03/01/logical-breakpoints-responsive-design/
当我读这篇文章的时候,这是我的第一个想法。
显然在这篇文章中投入了大量的工作——向 Eduardo 致敬!但我永远不会“预先指定”断点并根据它们设置内容的样式。
内容应该定义断点,而不是相反。
但是它确实允许你让内容像这样定义断点
我实际上认为它非常灵活和直观(对我来说)。
我马上就开始使用这个 mixin。非常感谢你的辛勤工作,Eduardo。
有时你没有了解内容的奢侈。特别是当你设计可配置的、针对每个客户定制的 Web 软件时。你需要知道内容,而不是真正地改变它。
就我个人而言,我不太喜欢以设备类别(平板电脑、手机等)命名的断点,原因很明显。(例如,屏幕宽度为 1024 像素的设备可以是手机到桌面之间的任何东西)
我认为 brad frost 的命名方法是最不差的解决方案:http://bradfrost.com/blog/post/7-habits-of-highly-effective-media-queries/#content
($bp-small, $bp-small-2, $bp-med, $bp-med-2,…)
我理解这一点,但这个解决方案的好处在于它足够灵活,可以做任何你想做的事情。你可以重新定义
$breakpoints
来使用这种命名约定,而不是它默认提供的设备名称。我真的认为内容应该定义断点,而不是一些预先确定的设备宽度,这些宽度会发生变化。设备的尺寸会继续变化,那么为什么不让设计中的元素随着宽度的变化而单独响应呢?你仍然可以通过一个 mixin 很简单地做到这一点……
使用
我必须同意。随着新版本的手机和平板电脑的出现(现在过于频繁),基于内容实现断点似乎是合乎逻辑的。使用这种方法,似乎可以减少网站维护的时间。
这正是我一直在使用的一种技术,它与 landon schropps 的技术相结合。首先进行移动设计也极大地简化了这一点。我使用了 break-one、break-two 等(类似于 schropps 的方法)作为一般指南,然后使用了 break-any,如你所述(它可能与 break-one 一起使用得最多),并且通常避免使用任何 max-width 媒体查询。
@Jason,伙计,这个 mixin 太棒了。
告别命名媒体查询!
关于基于像素的媒体查询与基于 em 的媒体查询,虽然 Chrome 中的浏览器“缩放”现在对两者都进行了相同的处理(例如,使用 ctrl/cmd +/-),但如果用户选择在浏览器设置中更改文本大小,基于像素的媒体查询将不会生效。出于这个原因,我仍然更喜欢基于 em 的媒体查询,尽管我不确定在进行缩放(浏览器或基于文本的缩放)的用户中,哪种情况更常见。
http://codepen.io/mpiotrowicz/pen/pvNNXM/
给你:http://bit.ly/1xD75tZ
确认!Monika 的演示按描述的方式工作。
当然,在 Firefox 中更改文本大小比在 Chrome 中更容易,并且可以查看结果。
感谢分享。
我通常会通过使用重置样式表来阻止他们修改浏览器设置。你可以更好地控制演示,并从等式中移除默认文本大小。
我会这样做。它具有使用常见断点和根据内容使用自定义断点的优点。从 Tim Knight 那里学到的。
我们如何使用它?:)
@Ricardo 它是一个条件语句
抱歉,这条评论应该是给 @Ricardo 的。
http://bit.ly/1xD75tZ
也许我是这里唯一一个这样做的,但我使用以下系统……
在 main.scss 中,我加载 general.scss,它包含所有情况下都需要使用的样式。接下来,也在 main.scss 中,但位于 general 导入的下方,我列出了所有断点,其中每个条件都导入该特定断点的“子”SCSS 文件。
因此 general.scss 包含通用(S)CSS,通常是移动优先样式,然后在更大的断点中根据需要扩展这些样式。我喜欢这种清晰的分离。断点代码没有任何重复,并且在基本样式和扩展样式之间有清晰的区别。事实上,从这些“子”SCSS 文件的大小就可以看出设计的质量。它们越大,设计实际上响应性越低。
仁者见仁,智者见智,但我很好奇为什么人们使用上述所有方法,对我来说,这些方法似乎没有必要?我的方法唯一的缺点是,如果你想完全了解一个选择器,你需要打开多个文件,但在实践中,这对我来说不是问题。
你介意分享一下这些文件,以便我们了解你的结构吗?
@Ricardo:当然,这是 projectname.scss 的内容,这是我的主 SCSS 文件
正如你所见,我首先加载基本样式,这些样式按功能划分到不同的文件中。所有这些基本样式的组合本质上就是移动优先状态。
接下来,断点来了,根据视窗,逐步导入额外的 SCSS 文件。这些文件基本是累加的,所以如果我们在“M”断点上,在这种情况下,它将加载 base + XXS + XS + S + M。
这些断点文件只包含 CSS 覆盖,不包含基本样式,当然,只包含与该断点相关的覆盖。
如前所述,我喜欢这种方法,因为我从未在任何地方看到媒体查询,我从未重复它们。我只是在正确的文件中工作。另一个主要好处是它可以很好地概述项目的健康状况。例如,如果我需要在每个断点中强烈地覆盖一个类,那么这个类可能从一开始就没有设计好。此外,如果一个断点 SCSS 文件变得过大,它也意味着有麻烦。
在我使用的(非常庞大的)项目中,大多数这些断点文件最多包含几十行覆盖,唯一的例外是 L 断点,它带来了根本性的布局变化,因此有很多覆盖。
我看到这种方法的唯一缺点是,有些人可能更愿意在一个文件中看到一个类(包括断点覆盖)。我不喜欢。我将基本样式与覆盖样式清楚地分开。
这有帮助吗?
@Ferdy,是的,很酷。
我必须承认,我会是那些希望在一个文件中看到一个类及其所有断点的人之一。:p
你这样做的方式与 Chris 之前展示的他的方法非常相似,其中主文件只是一堆
@import
。我知道我在 CSS-Tricks.com 上看到过类似的东西,但找不到页面。离题 –
我绝不是 Sass 专家,也许你已经知道这一点,但对于那些不知道的人,你可以使用单个
@import
指令将所有导入的文件连接起来。或者为了可读性而堆叠起来
感谢分享以及详细的解释。
抱歉,我的 Markdown 格式错误。
这是我使用的 Gruntfile
离题
chris 我真的很喜欢 css-tricks 的新外观,它加载速度也快了很多
这篇文章写得很好,媒体查询现在已经成了家常便饭,但现在用 SASS 来使用它会让代码变得更复杂。我对自己 CSS 的强迫症很相似,所以我希望它能简洁明了,易于理解。
很乐意看到这个超级混合宏被翻译成 Stylus,因为它在各个方面都比 Sass 好。
我本人也是 Stylus 的忠实粉丝。除了我用它来做我的节点应用程序,你如何在非节点项目中使用它?
我用 gulp-stylus 来编译它成 css
我对你说 Stylus 在各个方面都比 Sass 好的说法很感兴趣。你能详细说明一下吗?是什么让 Stylus 更出色?我从未使用过 Stylus - 我曾短暂使用过 LESS,但后来又回到了 Sass (SCSS)。
doh >_< ,真不敢相信我连这都没想过,哈哈,直到现在我才想到在非节点项目中使用它 Stylus 是我个人在 express 项目中使用过的,它和老式的 SaSS 有很多相同的语法。我喜欢它的语法,因为它非常像 Ruby。至于它是否更好,这见仁见智,我个人很喜欢它。
Stylus 可以做到 Sass 可以做到的一切。选择 Sass 而不是 Stylus 的唯一理由是你真的非常喜欢写大量的额外花括号、冒号、分号和“@mixin”这个词。
我喜欢 SCSS 语法,主要是因为它很熟悉,而且与我从 1998 年左右就开始写的 CSS 一致!旧习惯难以改掉 :) 而且,我的文本编辑器帮我做了大部分工作。
@Darryl 那就用 SASS 语法吧。
我倾向于更多地使用
@include
而不是@mixin
:]我喜欢用 JS 在我的标签中添加一个“lg, md, sm, 或 xs”类。然后,我只是把它嵌套到我的 css 规则中,这样我所有的样式都集中在一起,而不是分散到一个单独的媒体查询区域。