Try   HackMD

2020/02/22 Sass/Scss 教學筆記: 有效率的切版(下) for迴圈、each、ifelse 與 function

綱要

本週開始我們正式進入了用程式邏輯來撰寫樣式的篇章,要做到高效率切版就必須建立起複用性高、且容易擴充的共用組件,而在這之後的課程中也會陸續帶領同學們建立自己的 Sass Library。

這周我們就先來看看如何利用程式邏輯幫助建構樣式吧!

@for迴圈的應用

語法: @for $var from Num1 through/to Num2

$var可以自定義變數,每次回傳數字類型的結果。
through 包含Num2在內的範圍
to 不包含Num2在內,例如1 to 24只會回傳到23為止

flex示例

@for $i from 0 through 24 { .flex_#{$i} { flex: $i; } }
/* 編譯後的結果 */ .flex_1 { flex: 1; } .flex_2 { flex: 2; } .flex_3 { flex: 3; } ... ...

padding 內留白的示例

@for $i from 0 through 4 { /* 複習! #{} 的字符代表連接變數使用,{}中可以有運算式 */ [data-inset="#{$i * 0.5 + 'rem'}"] { padding: #{$i * 0.5 + 'rem'}; box-sizing: border-box; } }
/* 編譯後的結果 */ [data-inset="0rem"] { padding: 0rem; } [data-inset="0.5rem"] { padding: 0.5rem; } ... ... [data-inset="2rem"] { padding: 2rem; }

@each的應用

語法: @each $var in Array

$var可以自定義變數,每次回傳Array對應的值。
Array 陣列,可以直接在in的後面接'icon_1', 'icon_2'...
或者直接另外定義變數存放:

$iconSet: ['icon_1', 'icon_2', 'icon_3'];

@each會自動以Array中有幾個值,每次由左至右回傳對應位置的值

圖片路徑示例: 不同圖檔名稱的分配

$iconSet: ['icon_1', 'icon_2', 'icon_3']; @each $currentIcon in $iconSet { .icon_#{$currentIcon} { background: url('../img/#{$currentIcon}.svg') no-repeat center; } }
/* 編譯後的結果 */ .icon_icon_1 { background: url("../img/icon_1.svg") no-repeat center; } .icon_icon_2 { background: url("../img/icon_2.svg") no-repeat center; } .icon_icon_3 { background: url("../img/icon_3.svg") no-repeat center; }

圖片路徑示例: 加上不同裝置版本路徑的分類

如果你的圖檔包需要拆分不同裝置使用的路徑,@each裡面還可以再有一層@each做巢狀結構

$iconPath: ['pc', 'mobile', 'app']; $iconSet: ['icon_1', 'icon_2', 'icon_3']; @each $currentPath in $iconPath { @each $currentIcon in $iconSet { .icon_#{$currentPath}_#{$currentIcon} { background: url('../img/#{$currentPath}/#{$currentIcon}.svg') no-repeat center; } } }
/* 編譯後的結果 */ .icon_pc_icon_1 { background: url("../img/pc/icon_1.svg") no-repeat center; } .icon_pc_icon_2 { background: url("../img/pc/icon_2.svg") no-repeat center; } .icon_pc_icon_3 { background: url("../img/pc/icon_3.svg") no-repeat center; } .icon_mobile_icon_1 { background: url("../img/mobile/icon_1.svg") no-repeat center; } .icon_mobile_icon_2 { background: url("../img/mobile/icon_2.svg") no-repeat center; } .icon_mobile_icon_3 { background: url("../img/mobile/icon_3.svg") no-repeat center; } .icon_app_icon_1 { background: url("../img/app/icon_1.svg") no-repeat center; } .icon_app_icon_2 { background: url("../img/app/icon_2.svg") no-repeat center; } .icon_app_icon_3 { background: url("../img/app/icon_3.svg") no-repeat center; }

以下是@each高級用法,我們可以在這定義類似JSON MAP的組件來模組化分配的內容。
結構會稍微有點複雜,但各位同學可以斟酌參考這個例子。

@each map-get()做按鈕樣式的例子

Sass自從更新了3.3ver後多了組件定義取值的方法,我們先建立一個樣式組件:

$btnComponent:( submit:( class: 'submit', color: #fff, bgColor: #111199, borderColor: #ccc ), cancel:( class: 'cancel', color: #333, bgColor: #fefefe, borderColor: #dedede ), danger:( class: 'danger', color: #fff, bgColor: #ff2200, borderColor: #ff0000 ) );

調用時的語法稍有改變:@each $name, $value in $config

/* $name 指的就是 $btnComponent 中的submit、cancel、以及danger*/ @each $name, $value in $btnComponent { // 使用 map-get() 將組件內的樣式取出 $class: map-get($value, class); $color: map-get($value, color); $bgColor: map-get($value, bgColor); $borderColor: map-get($value, borderColor); // 分配到指定的class上 .btn-#{$class}{ color: $color; background-color: $bgColor; border-color: $borderColor; &:hover{ /* 加亮函數lighten */ background-color: lighten($bgColor, 5%); /* 加深函數darken */ border-color: darken($borderColor, 5%); } } }
/* 編譯後的結果 */ .btn-submit { color: #fff; background-color: #111199; border-color: #ccc; } .btn-submit:hover { background-color: #1414b0; border-color: #bfbfbf; } .btn-cancel { color: #333; background-color: #fefefe; border-color: #dedede; } .btn-cancel:hover { background-color: white; border-color: #d1d1d1; } .btn-danger { color: #fff; background-color: #ff2200; border-color: #ff0000; } .btn-danger:hover { background-color: #ff381a; border-color: #e60000; }

進階的用法看起來稍微有點複雜,但說穿了只是把對應的名稱和值配出去給目標class而已。


@if的應用

語法: @if $var == (>, <, >=, <=, !=) value (and, or)(value2)
== 等於
> 大於
>= 大於或等於
< 小於
<= 小於或等於
!= 不等於
and 以及
or 或者

$var為自定義變數,判別是否符合Value
當有其他條件時,也可使用@else@else if分別表示 2 個以上條件。
但需要注意@else if會編譯出第一個符合value的結果,不會把所有符合條件的樣式做編譯。
@if後頭又接了@if則會把符合的值一併編譯,造成同樣的屬性會出現兩個以上不同的值。

舉個例子:

@for $i from 0 through 3 { .flex_#{$i} { flex: $i; @if $i == 0 { height: 100%; } @else if $i >= 2 { color: #fff; } @else if $i != 3 { color: #333; } } }
/* 編譯後的結果 */ .flex_0 { flex: 0; height: 100%; /* $i = 0 時加入的條件 */ } .flex_1 { flex: 1; color: #333; /* $i 不等於 3 時加入的條件 */ } .flex_2 { flex: 2; color: #fff; /* $i 大於或等於 2 時加入的條件 */ } .flex_3 { flex: 3; color: #fff; }

@else if看起來相當的正常,但如果後面接的是@if呢?

@for $i from 0 through 3 { .flex_#{$i} { flex: $i; @if $i == 0 { height: 100%; } @if $i >= 2 { color: #fff; } @if $i != 3 { color: #333; } } }
/* 編譯後的結果 */ .flex_0 { flex: 0; height: 100%; /* $i = 0 時加入的條件 */ color: #333; /* $i 不等於 3 時加入的條件 */ } .flex_1 { flex: 1; color: #333; /* $i 不等於 3 時加入的條件 */ } .flex_2 { flex: 2; color: #fff; /* $i 符合大於或等於 2 的條件 */ color: #333; /* $i 符合不等於 3 的條件 */ } .flex_3 { flex: 3; color: #fff; }

看得出來$i = 2color被編譯了兩次結果對吧?
而瀏覽器會以最後一個結果為主,這就可能造成不是我們要的樣式覆蓋了正確的樣式,也會讓編譯資源佔據不少垃圾影響效能。

根據不同版本提供不同字型的例子:

假設現在我們有一包字體對照檔

/* font.scss */ @charset "UTF-8"; @font-face { font-family: "pc_iconfonts"; src:url("fonts/pc_iconfonts.eot"); src:url("fonts/pc_iconfonts.eot?#iefix") format("embedded-opentype"), url("fonts/pc_iconfonts.woff") format("woff"), url("fonts/pc_iconfonts.ttf") format("truetype"), url("fonts/pc_iconfonts.svg#iconfonts") format("svg"); font-weight: normal; font-style: normal; } @font-face { font-family: "mobile_iconfonts"; src:url("fonts/mobile_iconfonts.eot"); src:url("fonts/mobile_iconfonts.eot?#iefix") format("embedded-opentype"), url("fonts/mobile_iconfonts.woff") format("woff"), url("fonts/mobile_iconfonts.ttf") format("truetype"), url("fonts/mobile_iconfonts.svg#iconfonts") format("svg"); font-weight: normal; font-style: normal; } ... ...
/* page.scss */ @import 'font' $fontName: 'iconFonts'; $path: ['pc', 'mobile', 'app']; @each $currentPath in $path { .page_#{$currentPath} { font-family: $currentPath + '_' + $fontName; /* app 用不到這兩個屬性所以我們設判斷式過濾 */ @if $currentPath != 'app' { -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } } }
/* 編譯後的結果 */ .page_pc { font-family: "pc_iconFonts"; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .page_mobile { font-family: "mobile_iconFonts"; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .page_app { font-family: "app_iconFonts"; }

@function的應用

語法: @function $var(args){ @return }

$var為自定義變數,後綴加上一個以上的參數args,通常作為計算用途。

計算padding的例子

@function setPadding($topBottom, $leftRight){ /* 直接return也可以,為了讓大家理解可以在裡面定義其他變數 */ $conbine: #{$topBottom * 1rem + ' ' + $leftRight * 1rem}; @return $conbine; } .container { padding: setPadding(2, 1); // 單位:rem }
/* 編譯後的結果 */ .container { padding: 2rem 1rem; }

本日後記

回家作業: RWD手機網頁排版
作業不要忘了下週 2/29 在課堂上繳交喔!每週都讓自己變強是最有成就感的事情!

從本週開始,大家應該都可以感覺得出來使用程式邏輯管理CSS是什麼樣的感受。
一直以來寫程式最容易上手的原則就是 Write less, do more.
等到累積足夠的經驗後能處理更多稍稍複雜的需求,回過神來發現自己其實已經走了很長一段路。


tags: Scss flexbox data-attribute