SASS优化响应式断点管理

当你需要搞定响应式布局时,一堆堆的媒体查询、大量的属性、属性值往往可以把你搞颠,SASS(或者诸如此类的预处理器)被认为是处理响应式断点的最佳利器。

说到响应式断点处理,很多种方式涌上心头,经常有人问哪种方式最优,正如前端开发领域的大多数问题一样,这个问题同样没有标准答案,我们需要具体问题具体分析。更确切的说,难度不在于提出一个系统,而是提出一个既足够灵活(适用大部分场合)又不非常复杂的系统。

在今天的文章里,我将给大家介绍若干种响应式布局断点的解决方案,每一种都经过实践验证,一些方案可能优于其他方案,我会把决定的权利交给大家。

1. 使用变量(With variables)

BootstrapFoundation 采用这种方式,首先定义变量,然后在媒体查询中使用变量。换句话说,你可以在配置文件或者其他地方定义变量以备使用。我们来看看 Bootstrap 怎么干的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// Defining values
$screen-sm-min: 768px;
$screen-xs-max: ($screen-sm-min - 1);
$screen-md-min: 992px;
$screen-sm-max: ($screen-md-min - 1);
$screen-lg-min: 1200px;
$screen-md-max: ($screen-lg-min - 1);
// Usage
@media (max-width: $screen-xs-max) { ... }
@media (min-width: $screen-sm-min) { ... }
@media (max-width: $screen-sm-max) { ... }
@media (min-width: $screen-md-min) { ... }
@media (max-width: $screen-md-max) { ... }
@media (min-width: $screen-lg-min) { ... }

Foudation 更进一步,使用跨范围的媒体查询,避免使用过多的 max-width 和 min-width

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
// Defining values
$small-range: (0em, 40em); /* 0, 640px */
$medium-range: (40.063em, 64em); /* 641px, 1024px */
$large-range: (64.063em, 90em); /* 1025px, 1440px */
$xlarge-range: (90.063em, 120em); /* 1441px, 1920px */
$xxlarge-range: (120.063em); /* 1921px */
// Defining media queries
$screen: "only screen" !default;
$landscape: "#{$screen} and (orientation: landscape)" !default;
$portrait: "#{$screen} and (orientation: portrait)" !default;
$small-up: $screen !default;
$small-only: "#{$screen} and (max-width: #{upper-bound($small-range)})" !default;
$medium-up: "#{$screen} and (min-width:#{lower-bound($medium-range)})" !default;
$medium-only: "#{$screen} and (min-width:#{lower-bound($medium-range)}) and (max-width:#{upper-bound($medium-range)})" !default;
$large-up: "#{$screen} and (min-width:#{lower-bound($large-range)})" !default;
$large-only: "#{$screen} and (min-width:#{lower-bound($large-range)}) and (max-width:#{upper-bound($large-range)})" !default;
$xlarge-up: "#{$screen} and (min-width:#{lower-bound($xlarge-range)})" !default;
$xlarge-only: "#{$screen} and (min-width:#{lower-bound($xlarge-range)}) and (max-width:#{upper-bound($xlarge-range)})" !default;
$xxlarge-up: "#{$screen} and (min-width:#{lower-bound($xxlarge-range)})" !default;
$xxlarge-only: "#{$screen} and (min-width:#{lower-bound($xxlarge-range)}) and (max-width:#{upper-bound($xxlarge-range)})" !default;
// Usage
@media #{$small-up} { ... }
@media #{$small-only} { ... }
@media #{$medium-up} { ... }
@media #{$medium-only} { ... }
@media #{$large-up} { ... }
@media #{$large-only} { ... }
@media #{$xlarge-up} { ... }
@media #{$xlarge-only} { ... }
@media #{$xxlarge-up} { ... }
@media #{$xxlarge-only} { ... }

两种方法各有一个不爽的地方,在 Bootstrap 里每次都要使用 max-width,在 Foundation 里我们需要使用插值变量这种又丑又烦的方式,我们需要想办法解决这些问题。

2. 使用独立 Mixin(With a standalone mixin)

《media queries in Sass 3.2》CSS-Tricks 里最火的文章之一,在这篇文章里 Chris Coyier 在借鉴 a former idea by Mason Wendella former idea by Jeff Croft 两文的基础上,如何使用 sass 实现响应式布局的断点管理。

命名断点是非常重要的,因为可以为抽象的数字赋予意义(你知道767px是什么意思吗,我不知道,直到我去使用小屏幕的时候才知道)。为什么 Bootstrap 和 Foundation 要使用变量呢,不也是为了给抽象的数字起个名字吗?

所以我们定义个 mixin,接收断点名作唯一的参数,返回媒体查询的内容。准备好了吗?走起。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@mixin respond-to($breakpoint) {
@if $breakpoint == "small" {
@media (min-width: 767px) {
@content;
}
}
@else if $breakpoint == "medium" {
@media (min-width: 992px) {
@content;
}
}
@else if $breakpoint == "large" {
@media (min-width: 1200px) {
@content;
}
}
}

然后,我们这样使用 mixin:

1
2
3
@include respond-to(small) { ... }
@include respond-to(medium) { ... }
@include respond-to(large) { ... }

这个方法是极好的(甄嬛体,老外也看?),原因有二:抽象数据有意义,大量断点集中管理。如果你想把“992px”改成“970px”,你不需要爬过每一个 css 文件,而只需更新 mixin,然后全部更新。

但是也还有两个问题:

  • 断点不容易从mixin里拿出来,放到配置文件里去
  • 冗余太多

3. 可配置的mixin(With a configurable mixin)

为了解决上面的两个问题,我们需要从断点 mixin 中抽出一个列表,只剩下 mixin 核心,然后这个列表就可以随便移动,或者扔到配置文件中。然后,使用 sass 3.3+ 中的 maps,我们可以方便的使用关联的属性和属性值。

1
2
3
4
5
$breakpoints: (
'small' : 767px,
'medium' : 992px,
'large' : 1200px
);

然后原来的 mixin 进行相应的修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@mixin respond-to($breakpoint) {
// Retrieves the value from the key
$value: map-get($breakpoints, $breakpoint);
// If the key exists in the map
@if $value != null {
// Prints a media query based on the value
@media (min-width: $value) {
@content;
}
}
// If the key doesn't exist in the map
@else {
@warn "Unfortunately, no value could be retrieved from `#{$breakpoint}`. "
+ "Please make sure it is defined in `$breakpoints` map.";
}
}

我们在修改 mixin 的同时也进行了一些提高,不要小看这些提高,我们加上了错误处理,如果在 maps 中没有找到断点值,将会弹出一个错误提示,这将便于我们开发过程中的调试。

我们让 mixin 变得更加精简,能很好的处理错误,同时我们去掉了一个功能——判断属性是否是你想要的(min-width, max-width, min-height等),这在移动优先的网页中没问题,因为我们仅仅需要 min-width。但是,如果需要查询其他属性,我们需要把这个功能加回来。为了达到这个目的,我想到了一个非常优雅的解决方案,同时并不增加复杂性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
$breakpoints: (
'small' : ( min-width: 767px ),
'medium' : ( min-width: 992px ),
'large' : ( min-width: 1200px )
);
@mixin respond-to($name) {
// If the key exists in the map
@if map-has-key($breakpoints, $name) {
// Prints a media query based on the value
@media #{inspect(map-get($breakpoints, $name))} {
@content;
}
}
// If the key doesn't exist in the map
@else {
@warn "Unfortunately, no value could be retrieved from `#{$breakpoint}`. "
+ "Please make sure it is defined in `$breakpoints` map.";
}
}

在这里,我们主要做了三个事情

  • 检查查询的断点在map中存在不存在
  • 如果存在,打印对应的媒体查询。
  • 如果不在,进行错误提示。

简单吧,如果我们回顾前面的两个缺陷,已经不再有 WET(Write Everything Twice) 问题,也不再有不灵活的媒体查询。但是还有一个问题,不支持复杂的媒体查询。复杂指的是涉及多个组件的查询(e.g. screen and (min-width: 767px))。我们上面这些方案除了第一种变量之外都不能很好的解决这个问题。

4. 使用外部工具(With an external tool)

最后一个同样重要的是,如果不想创建自己的 mixin,你可以使用外部的工具处理响应式布局的断点,有很多 sass 的扩展在这个方面做得很好。

SassMQ Breakpoint Breakup
MQ type *-width any any
No Query fallback yep yep yep
API complexity simple very simple medium
Code complexity very simple complexe simple
Extra Debug mode Singularity.gs

基本上是这样,如果发现有没有涉及的,记得一定告诉我。

  • SassMQ

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    // Configuration
    $mq-responsive: true;
    $mq-static-breakpoint: desktop;
    $mq-breakpoints: (
    mobile: 320px,
    tablet: 740px,
    desktop: 980px,
    wide: 1300px
    );
    // Example
    selector {
    @include mq($from: mobile) {
    property: value;
    }
    }
  • BreakPoints

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    $high-tide: 500px;
    $ex-presidents: 600px 800px;
    $surfboard-width: max-width 1000px;
    $surfboard-height: (min-height 1000px) (orientation portrait);
    selector {
    @include breakpoint($high-tide) {
    property: value;
    }
    }
  • Breakup

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    $breakup-breakpoints: (
    'thin' '(max-width: 35.999em)',
    'wide' '(min-width: 36em)',
    'full' '(min-width: 61em)'
    );
    selector {
    @include breakup-block('thin') {
    property: value;
    }
    }

5. 总结

我们在这篇文章里看到的这么些个方案,都有长有短,没有一个完美的方案。最后我觉得还是由你来决定怎么把握可用性和复杂性的平衡。

一句话,在合适的场合使用合适的工具。

本文转载自:http://blog.csdn.net/whqet/article/details/26564287
原文:《Managing Responsive Breakpoints with Sass》
作者:Hugo Giraudel,来自法国,著名 SASS 大牛,在 SassWay 等多个网站撰文推广 sass,是 SassyJSON、SassyMatrix 等多个开源项目的开发者,大家可以到他的官方网站github 上了解详情。
翻译:前端开发whqet,以意译为主,不当之处请大家批评指正。