【學習筆記】CSS 預處理器

背景

  • CSS 自誕生以來,基本語法和核心機制一直沒有本質上的變化,它的發展幾乎全是表現力層面上的提高。最開始 CSS 在網頁中的做用只是輔助性的裝飾,輕便易學是最大的需求;然而現在網站的複雜度已經不可同日而語,原生 CSS 逐漸讓開發者力不從心
  • 當一門語言的能力不足而用戶的運行環境又不支持其它選擇的時候,這門語言就會淪爲 「編譯目標」 語言,開發者將選擇另外一門更高級的語言來進行開發,而後編譯到底層語言以便實際運行
  • 因而在前端領域,CSS 預處理器應運而生,而 CSS 這門古老的語言以另外一種方式 「從新適應」 了網頁開發的需求

百花齊放

  • CSS 預處理器是一個能讓你經過預處理器本身獨有的語法來生成 CSS 的程序
  • 市面上有不少 CSS 預處理器可供選擇,且絕大多數 CSS 預處理器會增長一些原生 CSS 不具有或不完善的高級特性,這些特性讓 CSS 的結構更加具備可讀性且易於維護,當前社區表明的 CSS 預處理器主要有一下幾種:
    • Sass:2007 年誕生,最先也是最成熟的 CSS 預處理器,擁有 Ruby 社區的支持和 Compass 這一最強大的 CSS 框架,目前受 LESS 影響,已經進化到了全面兼容 CSS 的 Scss
    • Less:2009 年出現,受 Sass 的影響較大,但又使用 CSS 的語法,讓大部分開發者和設計師更容易上手,在 Ruby 社區外支持者遠超過 Sass,其缺點是比起 Sass 來可編程功能不夠,不過優勢是簡單和兼容 CSS,反過來也影響了 Sass 演變到了 Scss 的時代,著名的 Twitter Bootstrap 就是採用 Less 作底層語言的
    • Stylus:Stylus 是一個 CSS 的預處理框架,2010 年產生,來自 Node.js 社區,主要用來給 Node 項目進行 CSS 預處理支持,因此 Stylus 是一種新型語言,能夠建立健壯的、動態的、富有表現力的 CSS。比較年輕,其本質上作的事情與 Sass/Less 等相似

預處理器賦予的 「超能力」

文件切分

  • 頁面愈來愈複雜,須要加載的 CSS 文件也愈來愈大,有必要把大文件切分開來,不然難以維護
  • 傳統的 CSS 文件切分方案基本上就是 CSS 原生的 @import 指令,或在 HTML 中加載多個 CSS 文件,這些方案一般不能知足性能要求

模塊化css

  • 把文件切分的思路再向前推動一步,就是 「模塊化」,一個大的 CSS 文件在合理切分以後,所產生的這些小文件的相互關係應該是一個樹形結構
  • 樹形的根結節通常稱做 「入口文件」,樹形的其它節點通常稱做 「模塊文件」,入口文件一般會依賴多個模塊文件,各個模塊文件也可能會依賴其它更末端的模塊,從而構成整個樹形
  • 模塊化是一種很是好的代碼組織方式,是開發者設計代碼結構的重要手段,模塊能夠很清晰地實現代碼的分層、複用和依賴管理,讓 CSS 的開發過程也能享受到現代程序開發的便利

選擇符嵌套

  • 選擇符嵌套是文件內部的代碼組織方式,它可讓一系列相關的規則呈現出層級關係。在之前若要達到這個目的,寫法以下,這種寫法須要手工維護縮進關係,當上級選擇符發生變化時全部相關的下級選擇符都要修改;此外把每條規則寫成一行也不易閱讀,爲單條聲明寫註釋也很尷尬(只能插在聲明之間了)html

    .nav {margin: auto /* 水平居中 */; width: 1000px; color: #333;}
      .nav li {float: left /* 水平排列 */; width: 100px;}
          .nav li a {display: block; text-decoration: none;}
    複製代碼
  • 在 CSS 預處理語言中,嵌套語法能夠很容易地表達出規則之間的層級關係,爲單條聲明寫註釋也很清晰易讀前端

.nav {
     margin: auto  // 水平居中
     width: 1000px
     color: #333
     li {
         float: left  // 水平排列
         width: 100px
         a {
             display: block
             text-decoration: none
         }
     }
}    
複製代碼

變量

  • 在變量出現以前,CSS 中的全部屬性值都是 「幻數」,不知道這個值是怎麼來的、它有什麼意義等,有了變量以後就能夠給這些 「幻數」 起個名字了,便於記憶、閱讀和理解
  • 當某個特定的值在多處用到時,變量就是一種簡單而有效的抽象方式,能夠把這種重複消滅掉,變量讓開發者更容易實現網站視覺風格的統一,也讓 「換膚」 這樣的需求變得更加輕鬆易行
// 原生 CSS 代碼 
strong {
    color: #ff4466;
    font-weight: bold;
}
.notice {
    color: #ff4466;
}

// 用 Stylus 來寫
$color-primary = #ff4466

strong
    color: $color-primary
    font-weight: bold

.notice
    color: $color-primary
複製代碼

運算

  • 光有變量仍是不夠的,還須要有運算,若說變量讓值有了意義,運算則可讓值和值創建關聯
  • 有些屬性值跟其它屬性值是緊密相關的,CSS 語法沒法表達這層關係;而在預處理語言中可用變量和表達式來呈現這種關係
// 只能用註釋來表達 max-height 的值是怎麼來的,且註釋中 3 這樣的值也是幻數,還須要進一步解釋
// 將來當行高或行數發生變化的時候,max-height 的值和註釋中的算式也須要同步更新,維護起來很不方便
.wrapper {
    overflow-y: hidden;
    line-height: 1.5;
    max-height: 4.5em;  /* = 1.5 x 3 */
}

// 預處理語言來改良
// 在後期維護時,只要修改那兩個變量就能夠了
.wrapper
    $max-lines = 3
    $line-height = 1.5

    overflow-y: hidden
    line-height: $line-height
    max-height: unit($line-height * $max-lines, 'em')
 
 // 這種寫法還帶來另外一個好處:$line-height 這個變量能夠是 .wrapper 定義的局部變量也能夠從更上層的做用域獲取
// 這意味着 .wrapper 可向祖先繼承行高,而不須要爲這個 「只顯示三行」 的需求把本身的行高寫死
// 有了運算,就有能力表達屬性與屬性之間的關聯,它令代碼更加靈活、更加 DRY
$line-height = 1.5  // 全局統一行高
body
    line-height: $line-height
.wrapper
    $max-lines = 3
    max-height: unit($line-height * $max-lines, 'em')
    overflow-y: hidden
複製代碼

函數

  • 把經常使用的運算操做抽象出來,就獲得了函數
  • 開發者可自定義函數,預處理器本身也內置了大量的函數,最經常使用的內置函數應該就是顏色的運算函數,有了它們,甚至都不須要打開 Photoshop 來調色,就能夠獲得某個顏色的同色系變種了
  • 預處理器的函數每每還支持默認參數、具名實參、arguments 對象等高級功能,內部還可設置條件分支,可知足複雜邏輯需求
// 給一個按鈕添加鼠標懸停效果
.button {
    background-color: #ff4466;
}
.button:hover {
    background-color: #f57900;
}
// 很難分清 #ff4466 和 #f57900 這兩種顏色到底有什麼關聯
// 若代碼是用預處理語言來寫,那事情就直觀多了
.button
    $color = #ff9833
    background-color: $color
    &:hover
        background-color: darken($color, 20%)
複製代碼

Mixins

Mixins 是 CSS 預處理器語言中最強大的特性,簡單點來講 Mixins 可將一部分樣式抽出,做爲單獨定義的模塊,被不少選擇器重複使用node

Sass 的混合git

  • Sass 樣式中聲明 Mixins 時須要使用 @mixin,後面緊跟 Mixins 名,也能夠定義參數同時可給這個參數設置一個默認值,但參數名是使用 $ 符號開始且和參數值之間須要使用冒號:分開
  • 在選擇器調用定義好的 Mixins 須要使用 @include,而後在其後緊跟要調用的 Mixins 名,不過在 Sass 中還支持老的調用方法,就是使用加號 + 調用 Mixins,在 + 後緊跟 Mixins 名
// 聲明一個 Mixins 叫做 error 
@mixin error($borderWidth: 2px) {
  border: $borderWidth solid #f00;
  color: #f00;
}

// 調用 error mixins
.generic-error {
  @include error(); /*直接調用error mixins*/
}
.login-error {
  @include error(5px); /*調用error mixins,並將參數$borderWidth的值重定義爲5px*/
}
複製代碼

Less 的混合github

  • 在 Less 中,混合是指將定義好的 ClassA 中引入另外一個已經定義的 Class,就像在以前的 Class 中增長一個屬性
  • LESS 樣式中聲明 Mixins 和 Sass 聲明方法不同,它更像 CSS 定義樣式,在 Less 可將 Mixins 當作是一個類選擇器,固然 Mixins 也能夠設置參數並給參數設置默認值,不過設置參數的變量名是使用 @ 開頭,一樣參數和默認參數值之間須要使用冒號:分隔開
// 聲明一個 Mixin 叫做 error
.error(@borderWidth: 2px){
  border: @borderWidth solid #f00;
  color: #f00;
}
// 調用 error Mixins
.generic-error {
  .error(); /*直接調用error mixins*/
}
.login-error {
  .error(5px); /*調用error mixins,並將參數@borderWidth的值重定義爲5px*/
}
複製代碼

Stylus 的混合編程

  • Stylus 中的混合和前兩款 CSS 預處理器語言的混合略有不一樣,它可不使用任何符號就直接聲明 Mixins 名,而後在定義參數和默認值之間用等號 = 來鏈接
// 聲明一個 Mixin 叫做 error
error(borderWidth=2px){
  border: borderWidth solid #f00;
  color: #f00;
}
// 調用error Mixins
.generic-error {
  error(); /*直接調用error mixins*/
}
.login-error {
  error(5px); /*調用error mixins,並將參數$borderWidth的值重定義爲5px*/
}
複製代碼

以上三個示例都將會轉譯成相同的 CSS 代碼瀏覽器

.generic-error {
  border: 2px solid #f00;
  color:#f00;
}
.login-error {
  border: 5px solid #f00;
  color: #f00;
} 
複製代碼

缺點

  • 額外的編譯配置:在寫樣式前須要作一些額外的編譯配置工做,sass-node 安裝以及編譯的配置就能卡住一批前端新手 image.pngsass

  • 編譯成本:每次修改代碼都須要從新編譯,佔用時間和 CPU image.pngmarkdown

  • 學習成本:不一樣的 CSS 預處理器語法不一樣,增長學習成本。在同一個團隊甚至項目裏,可能同時使用了好幾種樣式預處理器

    // Sass
    $color: #f00;
    $images: "../img";
    @mixin clearfix {
      &:after {
        content: " ";
        display: block;
        clear: both;
      }
    }
    body {
      color: $color;
      background: url("#{images}/1.png");
      @include clearfix;
    }
    
    // Less
    @color: #f00;
    @images: "../img";
    .clearfix() {
      &:after {
        content: " ";
        display: block;
        clear: both;
      }
    }
    body {
      color: @color;
      background: url("@{images}/1.png");
      .clearfix;
    }
    複製代碼
  • 調試:在使用 CSS 預處理器時,一般會配置 SourceMap 來輔助調試,但即便這樣,仍是會碰到一些調試困難的狀況

迴歸 CSS

各類 CSS 預處理器在更新迭代的過程當中功能愈來愈繁雜花哨,但絕大部分人用到的核心功能仍是那幾樣:Variables、Mixing、Nested、Module,頂多再加上一些工具類函數。既想要預處理器的優勢,又不想要它帶來的成本和缺點,有沒有一箭雙鵰的辦法?CSS 這麼多年一直也在從社區汲取營養加速進化和迭代,能不能從 CSS 標準裏面找到答案呢?

Variables in CSS

  • CSS 自定義屬性(CSS Custom Properties),又叫 CSS 變量(CSS Variable),容許在樣式中聲明變量並經過 var() 函數使用
  • CSS Custom Properties for Cascading Variables 規範在 2012 年 10 月首次做爲 工做草案(WD) 提出,並在 2015 年 10 月到達候選人推薦標準(CR)階段,如今瀏覽器支持程度已經接近 93%
  • CSS 變量定義及使用以下所示,可定義的類型極其豐富,不一樣於 SASS 預處理器變量的編譯時處理,CSS 變量是瀏覽器在運行時進行處理的,所以 CSS 變量會更增強大和靈活
/* declaration */
--VAR_NAME: <declaration-value>;
/* usage */
var(--VAR_NAME)

/* root element selector (global scope), e.g. <html> */
:root {
  /* CSS variables declarations */
  --main-color: #ff00ff;
  --main-bg: rgb(200, 255, 255);
  --logo-border-color: rebeccapurple;

  --header-height: 68px;
  --content-padding: 10px 20px;

  --base-line-height: 1.428571429;
  --transition-duration: .35s;
  --external-link: "external link";
  --margin-top: calc(2vh + 20px);
}
body {
  /* use the variable */
  color: var(--main-color);
}
複製代碼

爲何變量的定義以 -- 開頭?緣由在這裏:Let’s Talk about CSS Variables

Operators

  • 可使用 calc() 進行計算
:root {
  --block-font-size: 1rem;
}

.block__highlight {
  /* WORKS */
  font-size: calc(var(--block-font-size)*1.5);
}
複製代碼

Generate Colors

能夠用於經過 RGB 等函數生成和計算顏色:Generate Colors

CSS to JS

CSS 變量出現前,從 CSS 傳值給 JS 很是困難,甚至須要藉助一些 Hack 的手法。現使用 CSS 變量,可直接經過 JS 獲取變量值並進行修改

.breakpoints-data {
  --phone: 480px;
  --tablet: 800px;
}
const breakpointsData = document.querySelector('.breakpoints-data');

// GET
const phone = getComputedStyle(breakpointsData).getPropertyValue('--phone');

// SET
breakpointsData.style.setProperty('--phone', 'custom');
複製代碼

Custom Theme

  • 使用 CSS 變量,定製和動態切換網站主題很是簡單方便
  • 首先定義好不一樣主題下的變量,而後正常書寫樣式便可
  • 經過 JS 改變元素屬性,動態切換主題
html {
  --hue: 210; /* Blue */
  --text-color-normal: hsl(var(--hue), 77%, 17%);
  ...
}
html[data-theme='dark'] {
  --text-color-normal: hsl(var(--hue), 10%, 62%);
  ...
}
// 經過 JS 改變元素屬性,動態切換主題
document.documentElement.setAttribute('data-theme', 'dark')
document.documentElement.setAttribute('data-theme', 'light')
複製代碼

Mixins in CSS

  • CSS 的有一個提案:CSS @apply Rule,按照該草案描述,用戶可直接使用 CSS 變量存放聲明塊,而後經過 @apply rule 使用
  • 惋惜這個提案已被廢棄,具體廢棄緣由感興趣的能夠看看這篇文章:Why I Abandoned @apply。儘管 Mixins 如今 CSS 尚未好的實現標準,但咱們堅信早晚會有更優秀的規範涌現出來彌補 CSS 的這一塊空白
:root {
    --pink-schema: {
        color: #6A8759;
        background-color: #F64778;
    }
}

body{
  @apply --pink-schema;
}
複製代碼

Nesting in CSS

  • CSS 裏已經有 Nesting 的規範出現,儘管如今只處於 Editor’s Draft 階段:CSS Nesting Module Level 3,能夠看到按照 CSS Nesting Module ,Nesting 規範基本和預處理器如出一轍
/* Dropdown menu on hover */
ul {
  /* direct nesting (& MUST be the first part of selector)*/
  & > li {
    color: #000;

    & > ul { display: none; }

    &:hover {
      color: #f00;

      & > ul { display: block; }
    }
  }
}
複製代碼

Module in CSS

  • 其實 CSS 很早就有了模塊化方案,即 @import,使用 CSS 的 @import 規則可引用其餘的文件樣式,這特性從 IE 5.5 開始就被全部的瀏覽器支持,那爲何一直以來使用者寥寥無幾呢,緣由不少:
    • 在一些老的瀏覽器有加載順序的 bug
    • 沒法並行加載
    • 致使過多的請求數量
    • ...
  • 如今前端項目基本都使用構建工具(Gulp、Webpack 等)打包後再上線,所以以上哪些缺點也就不存在了,而在 Webpack 的 css-loader 中,是能夠配置是否開啓 @import 的

Selector Helpers

  • 除了上面介紹的一些主要特性,CSS 還提供了一些全新的特性來幫助更優雅的書寫樣式

:matches pseudo-class(已改名爲 :is())

  • :matches() CSS 僞類函數將選擇器列表做爲參數,並選擇該列表中任意一個選擇器能夠選擇的元素,這對於以更緊湊的形式編寫大型選擇器很是有用,並且瀏覽器支持程度也已經接近 93%
  • 想要了解更多詳情能夠查看規範:Selectors Level 4
/* 語法 */
:matches( selector[, selector]* )

.nav:matches(.side,.top) .links:matches(:hover, :focus) {
  color: #BADA55;
}

/* 至關於如下代碼 */
.nav.side .links:hover,
.nav.top  .links:hover,
.nav.side .links:focus,
.nav.top  .links:focus {
  color: #BADA55;
}
複製代碼
  • @custom-selector
    • 還可以使用自定義選擇器來定義能夠匹配複雜選擇器的別名
    /* 語法 */
    @custom-selector: <custom-selector> <selector-list>;
    複製代碼
    • 定義的方式和 CSS 變量相似,使用起來稍微有點區別
    @custom-selector:--text-inputs input[type="text"], input[type="password"];
    
    :--text-inputs.disabled,
    :--text-inputs[disabled] {
      opacity: 0.5
    }
    
    /* 至關於如下代碼 */
    input[type="text"].disabled,
    input[type="password"].disabled,
    input[type="text"][disabled],
    input[type="password"][disabled] {
      opacity: 0.5
    }
    複製代碼

用起來

  • 儘管上述的 CSS 特性還處於不一樣階段,瀏覽器的支持程度也不盡相同,但使用 postcss-preset-env,就能夠搶先嚐試 CSS 的最新特性
  • postcss-preset-env 的配置也十分簡單,以 Webpack 爲例
rules: [
  {
    test: /\.css$/,
    use: [
      'style-loader',
      { loader: 'css-loader', options: { importLoaders: 1 } },
      { loader: 'postcss-loader', options: {
        ident: 'postcss',
        plugins: () => [
          postcssPresetEnv(/* pluginOptions */)
        ]
      } 
    ]
  } 
]
複製代碼

總結

  • 通過一番梳理,儘管 CSS 在社區的刺激下加快了更新迭代的速度,但到目前爲止依然達不到 CSS 預處理器 VS CSS 的地步,只能說在使用 CSS 預處理器時,也可在項目中嘗試一些優秀的 CSS 新特性,即:CSS 預處理器 + CSS
  • 依然堅信,在 W3C 的推進下,隨着 CSS 自身不斷完善,CSS 預處理器終究會像當年的 CoffeScript 、Jade 同樣變成時代的過渡產物,到那時也就不用糾結各類 CSS 預處理器的環境配置和技術選型等,直接打開編輯器,就能愉快的書寫樣式
相關文章
相關標籤/搜索