Sass Mixin 和 Media Merging

若是你对 Sass不太熟悉的话,你可能不知道Sass增长了许多很是有趣的功能,例如媒体查询(即 @media)功能(常常被成为 Media Merging媒体合并)。css

Sass Logo

在向你解释什么是Media Merging以前,你应该了解Media Query(媒体查询)的 css规范是否容许嵌套媒体查询。一些浏览器支持嵌套媒体查询,例如:Firefox,Chrome和Opera;可是Safari和Internet Explorer目前并无支持嵌套媒体查询。前端

由于浏览器的支持不理想,因此Sass将嵌套的媒体查询合并到一个媒体条件中;例如,下面的代码:css3

@media (min-width: 42em) {
      @media (max-width: 1337px) {
        .foo {
          color: red;
        }
      }
    }

将编译为:浏览器

@media (min-width: 42em) and (max-width: 1337px) {
      .foo {
        color: red;
      }
    }

这样很方便,有木有?将嵌套的媒体查询合并到单个语句中时,这就是所谓的媒体合并。sass

咱们想要什么?Media queries(媒体查询)!

上面我已经作过简单的介绍了,如今让我来谈谈个人想法:基本上,我想构建一个很是简单的mixin,它将查询的映射做为输入,并将它们合并为@meida 指令中的单个条件做为输出。函数

回到前面的例子,我想这样写:oop

@mixin media($queries) { .. }
    
    .foo {
      @include media((min-width: 42em, max-width: 1337px)) {
        color: red;
      }
    }

在编译时,和上面的 CSS 代码段中看到的结果相同,在个人想法中,我能够至少有两种方式来建立它,让咱们先来解决看起来比较丑的一个。网站

丑陋的版本

最直接的想发就是构建一个字符串,咱们对每一个新的键值对进行迭代ui

/// Media query merger (the ugly version)
    /// Create a single media condition out of a map of queries
    /// @param {Map} $queries - Map of media queries
    
    
    @mixin media($queries) {
      $media-condition: ';
      
    
      // Loop over the key/value pairs in $queries
      
      
      @each $key, $value in $queries {
      
        // Create the current media query
        
        
        $media-query: '(' + $key + ': ' + $value + ')';
        
        
    
        // Append it to the media condition
        
        
        $media-condition: $media-condition + $media-query;
    
        // If pair is not the last in $queries, add a `and` keyword
        
        
        @if index(map-keys($queries), $key) != length($queries) {
          $media-condition: $media-condition + ' and ';
        }
      }
      
    
      // Output the content in the media condition
      
      @media #{$media} {
        @content;
      }
    }

虽然它的功能实现的很好,可是你必须认可,这样的代码不是很优雅。spa

优雅的方式

当Sass 提供这样一种优雅的方式来处理媒体查询时,我并不以为操做字符串的感受很舒服。确定还有一个更好的方法来作到这一点。而后他激发了我:递归。根据字典,递归的意思是:

一种定义对象序列(例如表达式,函数或集合)的方法,其中给出一些数量的初始对象,而且每一个连续对象根据先前对象来定义。

若是咱们仅仅是这样简单的理解,递归是一个函数用不一样的参数一遍又一遍地调用本身的机制,直到某一点。那么就有点艰难了。 在JavaScript中使用递归的函数的一个实际示例:

function factorial(num) {
      if (num < 0) return -1;
      else if (num == 0) return 1;
      else return (num * factorial(num - 1));
    }

正如你所看到的,函数调用本身,直到num变量小于1,每次运行时减小1。

为何我要告诉你这个?我想咱们可使用递归来构建咱们的媒体条件,使用Sass来进行媒体合并。若是咱们使mixin输出为map中的第一个查询媒体,而后调用自身传递map,直到map中没有查询为止。由于他有点复杂,因此咱们要一步一步的走。

首先,若是如今咱们的map没有更多的的查询,咱们只是输出内容。

@mixin media($queries) {
      @if length($queries) == 0 {
        @content;
      } @else {
        // ...
      }
    }

如今,咱们要在地图中输出第一个媒体查询的媒体块。要得到map的第一个键,咱们可使用nth(..) 和 map-keys(..)函数。

$first-key: nth(map-keys($queries), 1);
    
    @media ($first-key: map-get($queries, $first-key)) {
      // ...
    }

到目前为止还挺好,如今,咱们只须要使用mixin调用本身,咱们不要传递给他相同的$queries,不然咱们将面临一个无限循环。咱们须要在删除第一个键/值对后传递$ query。幸运的是,这里还有一个map-remove(..)函数。

$queries: map-remove($queries, $first-key);
    
    @include media($queries) {
      @content;
    }

如今整个mixin是这样的:

/// Media query merger
    /// Create a single media condition out of a map of queries
    /// @param {Map} $queries - Map of media queries
    
    
    @mixin media($queries) {
      @if length($queries) == 0 {
        @content;
      } @else {
        $first-key: nth(map-keys($queries), 1);
    
        @media ($first-key: map-get($queries, $first-key)) {
          $queries: map-remove($queries, $first-key);
    
          @include media($queries) {
            @content;
          }
        }
      }
    }

更进一步

前一篇文章中,咱们看到了几种不一样的方法来管理Sass中的响应断点。 在mixin使用的最后一个版本,以下所示:

/// Breakpoints map
    /// @type Map
    
    $breakpoints: (
      'small': (min-width: 767px),
      'medium': (min-width: 992px),
      'large': (min-width: 1200px),
    );
    
    
    /// Responsive breakpoint manager
    /// @param {String} $breakpoint - Breakpoint
    /// @requires $breakpoints
    
    
    @mixin respond-to($breakpoint) {
      $media: map-get($breakpoints, $breakpoint);
    
      @if not $media {
        @error "No query could be retrieved from `#{$breakpoint}`. "
        + "Please make sure it is defined in `$breakpoints` map.";
      }
    
      @media #{inspect($media)} {
        @content;
      }
    }

这个mixin像一个charm,但它不支持多条件查询,如(min-width:42em)和(max-width:1337px),由于它依赖于inspect(..)函数,只作打印 Sass表示的值。

所以,一方面,咱们有一个断点管理器从断点的全局map中选择并处理错误消息,另外一方面有一个断点管理器容许使用多查询条件。 选择是困难的。

经过稍微调整 respond-to(..) mixin,咱们可使他包括 media(..) mixin,而不是打印一个@media指令自己。

@mixin respond-to($breakpoint) {
      // Get the query map for $breakpoints map
      $queries: map-get($breakpoints, $breakpoint);
    
      // If there is no query called $breakpoint in map, throw an error
      @if not $queries {
        @error "No value could be retrieved from `#{$breakpoint}`. "
        + "Please make sure it is defined in `$breakpoints` map.";
      }
    
      // Include the media mixin with $queries
      @include media($queries) {
        @content;
      }
    }

最好的方法是,若是你已经使用了这个mixin,你能够经过调整respond-to(..)和添加 media(..)来完成包括多重查询功能,由于API 根本没有改变:respond-to(..) 和之前同样,仍须要一个断点名来工做。

最后的想法

我必须说,我以为这很是使人兴奋,由于这是我第一次找到一个很好的嵌套的媒体查询和mixin递归的样例。最后一个例子:

// _variables.scss
    
    $breakpoints: (
      'small': (min-width: 767px),
      'small-portrait': (min-width: 767px, orientation: portrait),
      'medium': (min-width: 992px),
      'large': (min-width: 1200px),
    );
    
    
    // _mixins.scss
    
    @mixin media($queries) { .. }
    @mixin respond-to($breakpoint) { .. }
    
    
    // _component.scss
    
    .foo {
      @include respond-to('small-portrait') {
        color: red;
      }
    }

将生成如下的CSS:

@media (min-width: 767px) and (orientation: portrait) {
      .foo {
        color: red;
      }
    }

做者信息

原文做者:Hugo Giraudel
原文连接:https://www.sitepoint.com/sas...
翻译自MaxLeap团队_前端研发人员:Ammie Bai
译者简介:新晋前端一枚,目前负责 MaxLeap 网站展现性内容的实现。喜欢本身尝试写一些js特效小Demo。

相关文章
无需Flash实现图片裁剪——HTML5中级进阶

做者往期佳做
简介 jCanvas:当 jQuery赶上HTML5 Canvas
如何结合Gulp使用PostCss

想要了解APP制做、开发?欢迎加入技术交流QQ群:480843919

相关文章
相关标签/搜索