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

相關文章
相關標籤/搜索