Sass Mixin 和 Media Merging

作者: 商领云 | 来源:发表于2016-11-01 09:39 被阅读103次

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

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

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

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

    将编译为:

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

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

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

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

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

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

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

    丑陋的版本

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

        /// 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;
          }
        }
    
    

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

    优雅的方式

    当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/sass-mixin-media-merging/
    翻译自MaxLeap团队_前端研发人员:Ammie Bai
    译者简介:新晋前端一枚,目前负责 MaxLeap 网站展示性内容的实现。喜欢自己尝试写一些js特效小Demo。

    相关文章

      网友评论

      本文标题:Sass Mixin 和 Media Merging

      本文链接:https://www.haomeiwen.com/subject/tkjputtx.html