最近感覺對 CSS 的熟悉程度大大上升了!但是發現我對 CSS 預處理器 (CSS Preprocessor) 還是停留在初見時的陌生,那種感覺大概就是我認識你,路上遇到你打個招呼然後就各奔東西了 XD
CSS 預處理器是什麼?他本質是為了解決單純撰寫 CSS 在遇到大型專案時可能出現語法重複、可維護性與可讀性不佳的問題。想進一步認識 CSS 預處理器可以看 "CSS 預處理器是什麼" 這篇文章,我覺得作者寫得非常易懂。
因為我還沒真正在一個正式專案使用過 Sass/SCSS,這篇文章的主要目的是整理 課程上的操作說明,為接下來可能用到的情況先做一個準備。
為什麼聽起來好像很複雜,還需要特別整理...?不是說 CSS 預處理會比 CSS 更簡潔、好讀嗎,這樣不是應該比較方便嗎?那是因為 Sass/SCSS 需要編譯啊,而且語法上還多了一點點不同,不常用的話如果不自己再記錄一下的話超容易忘記的。
啊提到編譯,那又是個啥?
呃...這個問題的出發點出主要是我們在撰寫 Sass/SCSS 後沒有辦法馬上套用在 HTML 元素上,因為咱們的瀏覽器認不出他是誰啊!必須透過編譯 (compile) 轉成 plain CSS,瀏覽器才看得懂,這種感覺就像是女朋友卸妝前和卸妝後的差別,然後你就是瀏覽器。
事不宜遲,先來安裝 Sass 編譯器吧!打開終端機輸入下面指令:
// 安裝 Sass 套件
npm install sass
// 看看你安裝的位置以及版本,確認是否安裝成功
npx which sass
npx sass --version
// 查看套件說明書
npx sass --help
然後請打開你的 VS code,建立下圖的資料夾環境來做測試。
先在 "main.scss" 中輸入下面指令模擬正要用作樣式處理的情境:
// main.scss
$bg_color : #b3d9ff;
$font_color : black;
body{
background-color:$bg_color;
color: $font_color;
}
接著在終端機中在輸入下列指令:
cd test/src
npx sass main.scss output.css
你會發現多跑出了兩個檔案,其中 "output.css" 就是已經編譯過的檔案,當你打開它會發現你剛剛在 "main.scss" 寫的樣式已經被轉成很熟悉的 CSS 樣式了:
// output.css
body {
background-color: #b3d9ff;
color: black;
}
上面這個是 "手動" 編譯一個 sass 檔案,如果我想要自動化呢?想要每次 .scss 檔案一有更動就自動編譯呢?沒關係,為了滿足人類的惰性,可以在終端機輸入這一行:
npx sass --watch main.scss:output.css
接著我們試著更動一下 main.scss:
// 把 #b3d9ff 改成 #b3dd2d
$bg_color : #b3dd2d;
你會發現 output.css 中的樣式也跟著換過去了:
假設我們有很多不同的 .scss 檔案要追蹤編譯呢?很顯然上面的做法就不是和我們了,但所幸開發者也早就想到這樣的情形,早早為大家準備了解方。
在這之前,先建立一個 "scss_dir" 資料夾,然後把 main.scss 移進去。
然後一樣打開終端機,輸入這一行指令:
npx sass --watch scss_dir:css_dir
會發現專案下多出了一個存放編譯過後檔案的資料夾 "css_dir" 啦!
為什麼要用 Webpack?因為當專案內容日漸龐大時,Webpack 可以更快速地幫助我們做編譯,然後打包成一包完整的檔案方便上線。
只要是談到 Webpack 的文章,多半都會有上面這張圖 XD 但這裡也只是紀錄如何用 Webpack 幫忙編譯 Sass,不是 Webpack 介紹,想知道的話推薦看 :
要使用 Webpack 記得要先裝 Node.js 喔!想知道怎麼裝的話看這裡。
裝好之後我們先來做專案初始化和安裝需要的套件吧!我們會需要安裝:
webpack 和 webpack-cli 負責打包,css-loader 和 sass-loader 負責編譯,mini-css-extract-plugin 負責把編譯後的 CSS 檔案抽出來。
// 初始化專案
npm init -y
// 安裝上列套件
npm install webpack webpack-cli mini-css-extract-plugin css-loader sass-loader
這時看一下專案內容,會有npm init -y已經建立好的資料夾和檔案:
然後接著我們要自己建立一下專案的其他架構:
打開 webpack.config.js,來寫 webpack 的設定檔囉 !需要注意的重點有幾個:
如果你有仔細看前面的Webpack教學 (一) :什麼是Webpack? 能吃嗎?這篇文章,會發現他的設定檔比起下面設定檔 (from: ALPHA camp) 少了 module 後面整段的設定,因為他的範例只是為了打包一個 JS 檔案而已。而 ALPHA camp 裡添加這段設定是指示所有以 ".scss" 為附檔名的檔案都會用 mini-css-extract-plugin、css-loader 及 sass-loader 來編譯。
// webpack.config.js
const path = require('path')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
entry: './src/main.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js'
},
module: {
rules: [
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'sass-loader'
]
}
]
},
plugins: [new MiniCssExtractPlugin()]
}
接這來到 package.json 檔案中,找到 script 部分加入這段:
"build": "webpack --mode production"
接著來建立要嘗試 Sass/SCSS 的檔案內容。
index.html:
<h1>Hello, Webpack Sass</h1>
main.scss:
$bg_color : #b3d9ff;
$font_color : black;
body {
background-color: $bg_color;
h1 {
color: $font_color
}
}
main.js:
import './scss/main.scss'
console.log('JS loaded!')
接著我們在終端機輸入npm run build
,會看到 dist 資料夾已經存放好編譯且打包好的檔案。
這時我們回到 index.html 引入打包後的兩個檔案:
<head>
<!-- ... -->
<link rel="stylesheet" href="/dist/main.css">
</head>
<body>
<!-- ... -->
<script src="/dist/bundle.js"></script>
</body>
然後打開預覽,就可以看到套用了樣式的 HTML 架構囉!
關於語法,強力建議先去看Learn Sass In 20 Minutes,會清晰很多。
而且影片一開始介紹了一個 VS code 的套件 - Live Sass Compiler,可以直接點下面操作欄的 "Watch Sass" 直接幫忙編譯成 CSS 檔案,超方便!
在介紹語法前,我們先準備一組 HTML 架構:
<header>
<h1>Hello, world!</h1>
<button>Click!</button>
</header>
<div>
<button>Submit</button>
<p>Hello, Taiwan</p>
</div>
變數的存在讓我們可以把很多元素都會用到的樣式統一儲存起來,這樣當專案龐大時我們不用針對一個個元素去修改 CSS 樣式,只要修改變數的部分就可以讓所有引用該變數的元素都套用這個樣式。而在 Sass/SCSS 中宣告變數是以 "$" 開頭。
$bgColor: lightblue;
header{
background-color: $bgColor;
}
如果說變數是儲存單一樣式,那函式就是儲存一組樣式。舉例來說,我們很常同時使用display: flex
, align-items: center
, justify-content: center
這樣一組樣式,那我們可以先把它儲存成函式,然後再來引用。
// 用 @mixin 宣告函式
@mixin flexSet{
display: flex;
align-items: center;
justify-content: center;
}
// 在選擇器中用 @include 引入
header{
background-color: $bgColor;
@include flexSet()
}
另外,函式也支援參數的引入,比如說我今天要讓flex-direction
透過參數來設定要 row 還是 column,我們可以把剛剛的 code 改成:
@mixin flexSet($direction){
display: flex;
align-items: center;
justify-content: center;
// add this
flex-direction: $direction;
}
header{
background-color: $bgColor;
@include flexSet(column);
}
往常我們在撰寫 CSS 時,假設我們要對 header 中的 h1 做樣式設定,在不給予 id 或 class 的情況下我們會這樣寫:
header h1{
color: white;
}
但在 Sass/SCSS 中,我們可以這樣寫:
header{
background-color: $bgColor;
@include flexSet(column);
// add this
h1{
color: white;
}
}
這樣的巢狀結構可以避免我們一直撰寫重複的開頭,也更讓人清楚上下元素的關聯性。
往更深入講,如果想使用偽元素呢?比如今天想要讓 header 裡的 button 在 hover 時背景變成紅色,一樣可以直接用巢狀結構撰寫:
header{
background-color: $bgColor;
@include flexSet(column);
h1{
color: white;
}
button{
&:hover{
background-color: red;
}
}
}
打個比方,今天 div 跟 header 的樣式幾乎一樣,只差在背景色的呈現,難道我們也要一一撰寫 div 的樣式嗎?
不用!Sass/SCSS 的繼承讓你直接把 header 的樣式引到 div 中:
div{
// 繼承 header 樣式
@extend header;
// 更改背景色
background-color: yellow;
}