SCSS/CSS 學習筆記

tags: OKR SCSS
2020/05/24(Update:2020/06/23)

網站資源

SCSS官方網站
SCSS官方網站-中文

Sass/Scss運作

早期Sass底層是以Ruby語言來撰寫,因此要使用Sass需要有Ruby環境,但官方已經於2019/03/26表示不再維護Ruby Sass!現階段比較主流的是DartSass及 LibSass,DartSass可讓Sass被編譯到JavaScript庫中,而LibSass最常見的就是因應Node.js而推出的Node-sass,目前我們正在使用的則是LibSass。

Scss與Sass差異

CSS寫法

.menu{..}
.menu ul{..}
.menu li{..}
.menu a{..}

SCSS寫法

目前公司也是採用SCSS,優點如下:

  • 相關教學可參考SCSS官方網站
  • 因為撰寫風格跟CSS很像,巢狀式架構可把區塊群組化,但要記得階層性建議不要超過3~4層內會比較好,以避免影響網頁渲染效能。還有層次結構變多的,反而會讓CSS變的更不彈性,反而不好維護。
  • 可以兼容CSS,應用到比CSS更強大的變數功能。
.menu{..}
.menu ul{..}
.menu li{..}
.menu a{..}

SASS寫法

可少寫括號與分號,但若資料量越大後,自己閱讀性不太容易判別錯誤,目前也不太習慣使用這種寫法

.menu
  ..
  ul
    ..
  li
    ..
  a
    ..

SCSS引用管理

  • SCSS的好處是可以讓專案各頁功能所需要的樣式群組化,不用通通擠在同一支CSS底下去撰寫,可以寫入很多支的SCSS,藉由引用(import)來彙整編譯成一支CSS。
  • 參考學習sass-7+1結構

模組化管理

依專案所需規劃可建立一支專門引用SCSS的管理頁,這樣可以很清楚知道專案有引用那些SCSS

規劃檔案的結構

(有底線代表不會被編譯成一支檔,合併使用)
_variable.scss //專門放相關變數
_mixin.scss //專門放相關mixin函式
_mixinRwd.scss //rwd media
_base.scss //全域設定SCSS
_index.scss //專案頁面SCSS

例如建立一支main.scss
依據上方的結構,這頁專門import上述的scss==>最後編譯成main.css
引用格式如下:

@import "variable";
@import "mixin";
@import "mixinRwd";
@import "base";

//你也可以建立分類的資料夾
@import "/page/index";
@import "abstracts/variables", "abstracts/functions", "abstracts/mixins";

也可以導入CSS文件
Imports where the URL ends with .css.
Imports where the URL begins http:// or https://.
Imports where the URL is written as a url().

@import "theme.css";
@import "http://fonts.googleapis.com/css?family=Droid+Sans";
@import url(theme);

SCSS使用

支援類別(Value Types)

  • Numbers:可能有也可能沒有單位,例如12或100px。
  • Strings:可以帶或不帶引號,例如"Helvetica Neue"或bold。
  • Colors:可以通過其十六進制表示形式或名稱(如#c6538c或)來引用,也可以blue從函數(如rgb(107, 113, 127)或)返回hsl(210, 100%, 20%)。
  • Lists of values:可以由空格或逗號並且其可以被包含在方括號或沒有括號可言,等進行分離1.5em 1em 0 2em,Helvetica, Arial, sans-serif或[col1-start]。
  • boolean:true和false。
  • null:預設先給空值。
  • Maps:將值與鍵相關聯的地圖,例如("background": red, "foreground": pink)

註解功能

有兩種註解方式:

  • 使用「//」兩個斜線來備註內容,而這些內容不會被SCSS編譯出來,只有自己才會看得到。
  • 若是想要被顯示出來,則使用「 /* 標註文字內容 */ 」

文檔註解功能

在@mixin中我們可以增加各種功能詳細描述,可參考project_template公司公版,裡面正在慢慢擴充一些常用的@mixin,以便共享於各專案使用。
使用「///」符號來註解,更多的註解用途可參考SassDoc

@example 描述功能名稱,實際應用在什麼地方
@param 是用來記錄@mixin或function的參數
/// @example tab分頁樣式-4種樣式
/// @param {String} $tab-type 參數請填tab名稱,預設為'underLineTab'
/// @param {Size} $line_Height 參數請填tab底線高度, 預設為'3px'
/// @param {Color} $line_Color 參數請填底線顏色

變數功能 Variable

  • 客製變數設定,變數名稱前面記得都要加$符號
  • 相同的變數名稱,擺在後面的會蓋掉前面的。
  • 變數名稱,可以使用「 - 」「 _ 」,官方提到$font-size和&font_size都會引用相同的變數,請二選一。
  • 將變數放到相同需要對應的SCSS裡面,CSS編譯結果會自動顯示出來

基本變數寫法

//變數設定
$text-color:#3F51B5;  
$font-xl:20px;

//將變數放置對應地方
.abc{
  font-size: $font-xl;
  background: $text-color;
  border: 1px solid $text-color;
  color: red;
}

編譯結果如下

.abc{
    font-size:20px;
    background:#3F51B5;
    border:1px solid #3F51B5;
    color:red
}

進行數值運算calc()

  • 可以進行加減乘除的運算,也可能定位。
  • 必須注意在使用+和-的時候,在前後一定要加上空白,不然會被誤為是給了個負值。*和/可以不用加,但為了格式統一方便和閱讀建議還是加一下。
  • 要注意若是加上其它變數一起計算,要記得變數因為是字串,所以要記得要上#{變數名稱}
/* 加減乘除運算 */
width: calc(100% - 50px);

/* 定位 */
top:calc(50% - 1px);

/* 變數計算,記得加上字串辨識 */
line-height: calc(#{$select-height} - 15px);

選擇器selector應用組合

符號可以放在第一層,也可以放第二層,或是在兩者的之間

ul > { 
  li {
    list-style-type: none;
  }
}

h2 {
  + p {
    border-top: 1px solid gray;
  }
}

p {
  ~ {
    span {
      opacity: 0.8;
    }
  }
}

  • 星號*可以同時被用於子對象選擇器中
#container * {
 border: 1px solid black;
}
  • X+Y 相鄰兄弟選擇器
    選擇後面同層的第一個元素class賦予狀態
/* h2相鄰的兄弟p,增加border-top灰線 */
h2 {
  + p {
    border-top: 1px solid gray;
  }
}
  • X~Y 相鄰兄弟選擇器
    選擇後面同層的所有相同class賦予狀態
/* h2相鄰的所有兄弟p,增加border-top灰線 */
p {
  ~ {
    span {
      opacity: 0.8;
    }
  }
}
  • &替代符號
p {
    &:hover{}
    .active &{}
    :not(&){}
}
/* 編譯結果 */
p:hover{}
.active p{}
:not(p){} //選擇所有不是p的元素,[補充]:not它的作用是防止特定的元素被選中

&符號搭配BEM結構撰寫時非常好用,而無須多次撰寫較長的class,看以下範例就可以得知,目前公司專案也是儘量採BEM結構模式撰寫。

.accordion{
    /* ... */
 &__copy{
     /* ... */
     &--open{
        /* ... */
     }
 }
}
/* 編譯結果 */
.accordion{/* ... */}
.accordion__copy{/* ... */}
.accordion__copy--open{/* ... */}
  • #{}插入值
    通常會應用在放入className,或是css名稱,css名稱可拿來應用在有多種屬性值時,例如left/right/top/bottom,範例如下
@mixin xxx($className,$top-or-bottom,$left-or-right){
    .icon-#{$className}{
        #{$top-or-bottom}:0;
        #{$left-or-right}:0;
    }
}
@include xxx('mail',top,right)
/* 編譯結果 */
.icon-mail{
    top:0;
    right:0;
}
  • CSS-nth-child選擇器
/* first-child代表第一個 */

 li:first-child {
    border: 0;
    border-top-left-radius: $border-radius;
    border-top-right-radius: $border-radius;
}

/* last-child代表最後一個 */
li:last-child {
    border: 0;
    border-bottom-left-radius: $border-radius;
    border-bottom-right-radius: $border-radius;
}

/* nth-child(n)代表指定選擇第幾個,從(1)開始 */
li:nth-child(1) {
    border: 0;
    border-bottom-left-radius: $border-radius;
    border-bottom-right-radius: $border-radius;
}

/* nth-child(odd)奇數 */
p:nth-child(odd) {
    background: red;
}

/* nth-child(even)偶數 */
p:nth-child(even) {
    background: blue;
}

/* :nth-child(an+b)若是使用計算方式,n則可從(0)開始 */
tr:nth-child(3n+1){
    background-color:#69C;
}
  • unique-id()自動產出名稱
    官方有這個功能,會自動產生9個字串的字母數字,例如"uat4r38ew",雖然目前未曾實際應用於專案上,但有篇文章提到可以使用在CSS Cache上感覺滿適合的,可參考下方連結:
    SCSS Unique Id for Cache Busting your CSS
    所謂CSS Cache緩衝是以前有曾經遇過的狀態,就是SCSS內裡面可能會引用CSS/圖片等,但有可能發生有檔案名稱一樣,但有新版本資料,卻發生明明已更新資料,但網頁上看到的是舊資料問題,這時候就可以透過添加版本參數來更新。
/* 上方文章分享-圖片緩衝 */
$ver:unique_id();
@mixin imageCacheBust($url) {
   background-image: #{'url("'}#{$url}#{'?v='}#{$ver}#{'")'};
}

.sprite {
    @include imageCacheBust('asset/images/logo.png');
}
/* 編譯結果 */
.sprite {
    background-image: url('asset/images/logo.png?v=u95ab40e0');
}

Sass Map變數組合應用

Sass map是類似json的一種變數,可以使用Sass Map來定義基本的元件變數,與json最大的差異是使用()取代{}

三種function

//列出指定map的key
map-keys($map)

//列出指定map的value
map-values($map) 

//判別布林值true or false
map-has-key($map, $key)
$color:(
 primary:#cccccc;
 secondary:#000000;
)

/* 編譯結果 */
//以下只是展示範例回傳的結果,僅供參考
map-keys($color,primary);
.test{
    color:primary
}
map-values($color,#cccccc);
.test{
    color:#cccccc
}
map-has-key($color,primary);
.test{
    color:true
}
範例一顏色組合
/* variables組合 */

//"$key":$value
//可以依據不同的元件用一個群組,方便辨識,例如border.button.text.link..等等
$primary-color: #098081;
$primary2-color: #c4dbd5;
$primary3-color: #6e6d6d;

//可以在依網站的顏色去自訂名稱,方便記憶
$border-set:(
 "border-primary1": $primary-color,
 "border-primary2": $primary2-color,
 "border-primary3": $primary3-color
);
/* variables使用 */

//使用map-get()取值
.button {
  color:map-get($border-set,border-primary3)
}
範例二按鈕組合

上面是只有單純顏色的組合,若像是按鈕這種會包含多樣式種類的,可以寫成以下組合

/* variables組合 */

// 定義按鈕的不同狀態設定
$btn-config:(
	default:(
		class: 'default',
		color: #333,
		bg: #fff,
		border-color: #ccc
	),
	primary:(
		class: 'primary',
		color: #fff,
		bg: #009AFF,
		border-color: #009AFF
	),
	danger:(
		class: 'danger',
		color: #fff,
		bg: #D84315,
		border-color: #009AFF
	)
);

然後賦予基本button的結構+搭配mixin()來完成多組按鈕組合,詳細可參考分享網站透過SassMap製作多個樣式

/* 首先刻出一個按鈕基本結構 */
.btn{
    /*......*/
    &:hover,&:focus{
        color: #333;
        background-color: #e6e6e6;
    }
}

/* @mixin */
//通常按鈕結構最常變更的是背景顏色、外框顏色、還有文字顏色
@mixin button-set($color,$background,$border-color){
    color:$color;
    background-color:$background;
    border-color:$border-color;
    &:hover,&:focus,&:active{
        color:$color;
        background-color:darken($background,5%);
        border-color:darken($border-color,5%);
    }
}
/* @each+map-get()+@include */
//$key,$value
@each $keyname, $value in $btn-config{
    $class:map-get($value,class);
    $color:map-get($value,color);
    $bg:map-get($value,bg);
    $border-color:map-get($value,border-color);
    
    .btn-#{$class}{
        @include button-set($color,$background,$border-color)
    }
}
/* html結構 */

//這樣就可以寫出類似像bootstrap btn不同class的btn
<button class="btn">這是一個按鈕</button>
<button class="btn btn-primary">這是一個按鈕</button>
<button class="btn btn-danger">這是一個按鈕</button>

好用的SCSS組合應用

mixin相類似參數寫法

若CSS有相類似的語法,範例如下

/* 原本這樣寫 */
@mixin banner($image,$bg-size: cover, $bg-height: 500px, $bg-position: 50% 50%) {
    backgorund-image:$image;
    background-repeat: no-repeat;
    background-size: $bg-size;
    background-position: $bg-position;
    max-width: 100%;
    height: $bg-height;
}
/* 後來這樣寫 */
@mixin banner($image,$bg-size: cover, $bg-height: 500px, $bg-position: 50% 50%) {
    max-width: 100%;
    height: $bg-height;
    
    background:{
        image:$image;
        repeat:no-repeat;
        size:$bg-size;
        position:50% 50%;
    }
}

@mixin+@if布林值判斷

  • 返回true或false
  • @else if or @else
/* mixin建立組合 */
@mixin avatar($size, $circle: false) {
  width: $size;
  height: $size;

  @if $circle { 
    border-radius: $size / 2; //返回true
  }
}

.square-av { @include avatar(100px, $circle: false); }
.circle-av { @include avatar(100px, $circle: true); }
/* 編譯結果 */
.square-av {
  width: 100px;
  height: 100px; //返回false,所以沒有border-radius
}

.circle-av {
  width: 100px;
  height: 100px;
  border-radius: 50px; //返回true
}

另一種寫法
例如判斷是否需要border-radius(沒有外框為0/有外框就給數值),寫法如下:

/* mixin建立組合 */
@mixin square($size, $radius: 0) {
  width: $size;
  height: $size;

  @if $radius != 0 {
    border-radius: $radius; //假如不等於0
  }
}

.text{
 @include square(100px,$radius:4px);
}
/* 編譯結果 */
.text {
  width: 100px;
  height: 100px;
  border-radius: 4px;
}

區塊共用

有時網頁會有很多區塊會共用到不同的背景或圖片樣式,這時候都還要特別幫那個區塊給一個class或是直接將css寫在那個區塊裡,使用mixin()來建立組合,有需要就直接引用到區塊裡

/* mixin建立組合 */
@mixin section-background($color){
 #{if (&, '&.section-background', '.section-background')}{
     background-color:$color;
     color:#000000;
 }
}
/* include使用 */
.sidebar{
 @include section-background(#cccccc);
}
/* 編譯結果 */
.sidebar.section-background{
    background-color:#cccccc;
    color:#000000;
}

多類型@mixin合併+@error報錯提示

舉例來說,若有多種類型的tab分頁style,原本一種tab就寫一支@minxin,那要如何使用@mixin來合併,引用時只要判斷類型輸入即可呢?答案就是使用@if,範例說明如下:

/* mixin多類型建立組合 */

//可以在@mixin裡放置預置的變數
//日後若有新的tab種類,即可依序新增下去..
//@error為編譯時若有錯誤會編譯失敗並停止,顯示錯誤訊息提醒,@warn也是一種錯誤提醒,但sass雖會報錯,但還是會繼續編譯,所以反而使用@error會更好

@mixin navTab(
    $tab-type: underLineTab //預設的tab種類
    $selector: "*", //若父層底下有子層的內容,可以使用*來代表
){
    @if $tab-type == underLineTab {
        /*這裡放第一種tab的css*/
    }@else if $tab-type == borderTab{
        /*這裡放第二種tab的css*/
    }@else if $tab-type == squareTab{
        /*這裡放第三種tab的css*/
    }@else if $tab-type == nogutterTab{
        /*這裡放第四種tab的css*/
    }@else {
    @error "請填寫$tab-type正確參數:underLineTab/borderTab/squareTab/nogutterTab";
  }
}

@mixin+@content

網路上提到常見斷點參考
iPad - 768px
iPad以下 - 767px
iPhone 6 Plus - 414px (視專案族群)
iPhone 6 - 375px (視專案族群)
iPhone 5、SE - 320px

使用在rwd範例,以下參考bootstrap斷點設計

//rwd breakpoint 設定各種斷點,也可以在陸續新增自訂的尺寸
$col-xl: 1200px;
$col-lg: 992px;
$col-md: 768px;
$col-sm: 576px;

@mixin rwd($col-xl) {
  @media (max-width: $col-xl) {
    @content;
  }
}

@mixin rwd($col-lg) {
  @media (max-width: $col-lg) {
    @content;
  }
}

@mixin rwd($col-md) {
  @media (max-width: $col-md) {
    @content;
  }
}

@mixin rwd($col-sm) {
  @media (max-width: $col-sm) {
    @content;
  }
}
.circle-image2 {
  @include circle(blue, $white, $size);

    //一樣使用@include引用
    @include rwd($col-xl) {
    width: $size/2;
    height: $size/2;
    line-height: $size/2;
    font-size: 15px;
  }
  @include rwd($col-sm) {
    width: $size/2;
    height: $size/2;
    line-height: $size/2;
    font-size: 13px;
  }
}
//編譯結果
.circle-image2 {
  background: blue;
  border-radius: 99%;
  width: 500px;
  height: 500px;
  line-height: 500px;
  font-size: 100px;
  color: #fff;
  text-align: center;
}
@media (max-width: 1200px) {
  .circle-image2 {
    width: 250px;
    height: 250px;
    line-height: 250px;
    font-size: 15px;
  }
}
@media (max-width: 576px) {
  .circle-image2 {
    width: 250px;
    height: 250px;
    line-height: 250px;
    font-size: 13px;
  }
}

@each迴圈

@each <variable> in <expression> { }
each迴圈可以設定一個變數群組然後分別套用

$sizes: 40px, 50px, 80px;

@each $size in $sizes {
  .icon-#{$size} {
    font-size: $size;
    height: $size;
    width: $size;
  }
}
/* 編譯結果 */
.icon-40px {
  font-size: 40px;
  height: 40px;
  width: 40px;
}

.icon-50px {
  font-size: 50px;
  height: 50px;
  width: 50px;
}

.icon-80px {
  font-size: 80px;
  height: 80px;
  width: 80px;
}

替換背景顏色方法範例

<div class="container">
    <div class="box box1 box-primary"></div>
    <div class="box box2 box-primary"></div>
    <div class="box box3 box-danger"></div>
    <div class="box box4 box-secondary"></div>
    <div class="box box5 box-danger"></div>
</div>
//設定顏色群組
//"$key":$value,
$colorset: (
  "primary": #ccc,
  "danger": #dd4444,
  "secondary": #3e5bbb
);

//scss變數寫法:#{$key}
@each $key, $value in $colorset {
  .box-#{$key} {
    background: $value;
  }
}

顯示效果如下

@for迴圈

透過scss可以搭配@for迴圈來使用連續變數的功能,例如以下顏色漸變範例

<div class="container">
    <div class="box box1"></div>
    <div class="box box2"></div>
    <div class="box box3"></div>
    <div class="box box4"></div>
    <div class="box box5"></div>
</div>
//box基本屬性
.box {
  height: 150px;
  width: 150px;
  float: left;
  background: #ccc;
}

//box1~box5給予連續漸變顏色效果,設定$i(1~5)數值為變數
//@for迴圏 from-through(跑1到5數值);from-to(跑1到4數值),兩者須注意有不同
//scss變數寫法:#{$i}
@for $i from 1 through 5 {
  .box#{$i} {
    background: darken(white, $i * 10%);
  }
}

顯示效果如下

@debug 偵測錯誤

跟console.log一樣,幫你查看打印出來的值是否正常。以下為一個簡單的例子,實際專案下執行webpack編譯時就會看到訊息。但另外有發現Vscode Live Sass使用@debug不會出現,看了一下它的說明與更新內容好像本來就沒有進一步支援。

$font-sizes: 10px + 20px;
$style: (
  color: #bdc3c7
);

.container{
  @debug $style;
  @debug $font-sizes;
}
/* 打印結果,會出現類似畫面 */
Line 7 CSS: (color: #bdc3c7)
Line 8 CSS: 30px

@at-root 跳脫子層

可以將一個或多個樣式規式跳出子層,而不是嵌在父層selector裡面,範例如下:

.parent{
    ...
    @at-root .child{....}
}
/* 編譯結果 */
.parent{...}
.child{...}
.parent{
    ...
    .step-child { ... }
    @at-root{
        .child1{...}
        .child2{...}
    }
}
/* 編譯結果 */
.parent{...}
.parent .step-child{...}
.child1{...}
.child2{...}

@use與@forward

分享一篇看到寫的很清楚的文章,文章提到詳解Sass新特性- 模塊的內容。
內容提到sass團隊正在推行sass模塊化機制,建議大家使用@use.@forward,但目前僅Dart Sass 1.23.0完全支援這兩種特性,加上官網@import說明上也有提到Sass團隊不鼓勵繼續使用@import規則,預計在未來幾年會逐步淘汰它,雖然還不是現在,但或許在淘汰前可能要慢慢去研究@use+@forward。

文件中提到@use與@import最主要的差異(待須實際試用):
@import:假如不同的頁面引用了相同的scss,樣式可能會被重覆加載,導致重複代碼
@use:不管使用了多少次樣式表,都只會引用和執行一次