Sass應用之實現主題切換

瑪爾斯通發表於2022-06-19

Sass應用之實現主題切換

Sass實現主題切換

背景

實現主題切換有幾種不同的方案,比如使用CSS變數,使用JavaScript動態載入對應的主題樣式檔案等。本文主要講的是如何使用Sass實現主題切換。

前置知識

瞭解Sass的基本使用

  • variable
  • mixin
  • map

本質

Sass作為CSS前處理器,需要編譯成CSS後,才能被瀏覽器識別和解析。因此無法在瀏覽器中直接使用Sass實現類似CSS變數那種動態切換。本質上來說,專案中有幾個主題就要提前定義好幾份主題樣式並全部引入。

思路

首先,我們需要給應用的頂層元素新增一個主題標識,用於標識當前的主題,用於之後應用上對應的主題樣式。該標識可以是資料屬性,也可以是類,也可以是id,這裡採用資料屬性。

<html>
  <div class="app" data-theme="light"></div>
</html>

然後,每次切換主題時,通過更新該標識,頁面就會應用樣式檔案中提前定義好的對應的主題樣式。

.app {
  &[data-theme='light'] {
    color: #333;
  }
  
  &[data-theme='dark'] {
    color: #fff;
  }
}

實現

基礎版

基於主題切換的本質和思路,我們可以通過硬編碼,實現一個簡單的主題切換。

<div id="app" class="app">
  <h1 class="title">Hello, World</h1>
  <p class="subtitle">當前主題:<span id="theme-current">亮色</span></p>
  <button class="theme-switch light" data-theme="light">亮色</button>
  <button class="theme-switch dark" data-theme="dark">暗色</button>
</div>

首先給應用新增一個主題標識,這裡我通過給body元素新增一個資料屬性data-theme表示當前的主題。預設為light

<body data-theme="light">
  <div class="app"></div>
</body>

然後提前定義好所有主題樣式:

// 所有主題樣式
$bg-color-light: #ffffff;
$bg-color-dark: #091a28;
$title-color-light: #363636;
$title-color-dark: #ffffff;
$subtitle-color-light: #4a4a4a;
$subtitle-color-dark: cyan;

.app {
  // 預設主題樣式(light主題)
  background-color: $bg-color-light;
  
  // dark主題
  [theme='dark'] & {
    background-color: $bg-color-dark;
  }
}

.title {
  color: $title-color-light;
  
  [theme='dark'] & {
    color: $title-color-dark;
  }
}

.subtitle {
  color: $subtitle-color-light;
  
  [theme='dark'] & {
    color: $subtitle-color-dark;
  }
}

最後,當我們點選不同主題按鈕時,就會更新body上的主題標識data-theme,這樣,樣式檔案中對應的主題樣式就會被應用上了。

完整程式碼和實現效果可以參考Codepen:


SASS實現主題換膚/主題切換-基礎版
by zhouqichao (@Tom_chao)

不過該實現有點粗糙,存在幾個小問題:

  1. 每個需要應用主題樣式的CSS選擇器中,都要寫一遍對應主題需要的樣式,比較繁瑣
  2. 如果有多個主題,程式碼量會極具增加,並且很多都是重複的“模板程式碼”

針對這些問題,我們可以利用Sass的一些特性實現一個進階版的主題切換。

進階版

首先,針對基礎版暴露出的問題。我們需要對Sass變數做一點小小的調整。這裡我們將主題樣式封裝成了map格式,map中每一個元素都對應著不同主題下的樣式。

// 所有主題樣式
$bg-color: (
  // 亮色
  light: #fff,
  // 暗色
  dark: #091a28
);

$title-color: (
  light: #363636,
  dark: #ffffff
);

$subtitle-color: (
  light: #4a4a4a,
  dark: cyan
);

針對重複的模版程式碼和程式碼繁瑣的問題,Sass中有個特性mixin,正好可以利用上。

接下來,我們要封裝一個mixin,專門解決基礎版1-手寫程式碼的繁瑣的問題。

這裡使用了Sass中的插值表達#{}map-get方法,#{}類似於JavaScript中的計算屬性,可以動態設定屬性名,map-get方法用於從map中獲取某一個屬性對應的值。

@mixin themify($key, $valueMap) {
  // 預設主題
  #{$key}: map-get($valueMap, 'light');
    
  // dark主題
  [theme='dark'] & {
    #{$key}: map-get($valueMap, 'dark');
  }
}

themify主要封裝了預設主題樣式light,和dark主題樣式,這樣我們在選擇器裡,只需要include這些樣式即可。

.app {
  @include themify('background-color', $bg-color);
}

.title {
  @include themify('color', $title-color);
}

.subtitle {
  @include themify('color', $subtitle-color);
}

現在看這些程式碼是不是簡潔多了?省去了自己手寫那些繁瑣的模板程式碼!

針對“多主題模版程式碼會更多”的問題,解決起來也就很容易了。只需要簡單修改下該mixin,新增上對應的主題樣式即可。

@mixin themify($key, $valueMap) {
  // light主題
  #{$key}: map-get($valueMap, 'light');
    
  // dark主題
  [theme='dark'] & {
    #{$key}: map-get($valueMap, 'dark');
  }
  
  // dark1主題
  [theme='dark1'] & {
    #{$key}: map-get($valueMap, 'dark1');
  }
  
  // dark2主題
  [theme='dark2'] & {
    #{$key}: map-get($valueMap, 'dark2');
  }
}

當然,我們還可以對mixin做一下優化,可以將主題封裝成list格式,然後通過遍歷主題,簡化mixin:

@mixin themify($key, $valueMap) {
  // theme list
  $themes: light, dark;
  
  @each $theme in $themes {
    [theme=#{$theme}] & {
      #{$key}: map-get($valueMap, $theme);
    }
  }
}

這樣看起來就清爽多了。
完整程式碼和實現效果可以參考Codepen:

SASS實現主題換膚/主題切換-進階版
by zhouqichao (@Tom_chao)

總結

Sass作為一款流行的CSS前處理器,提供了插值表達#{}map型別等特性,在實現主題切換方面提供了不少便利。

當然,Sass實現主題切換還有很多可以優化的點。這裡隨便列兩條常見的:

  1. 如果有多條主題樣式需要應用,每一條都要寫一遍@include,感覺有點麻煩,能不能只寫一遍@include

    .app {
      @include themify('background-color', $bg-color);
      @include themify('color', $text-color);
      // ...
    }
    
    // 希望可以只寫一遍@include
    .app {
      @include themify(
        (
        'background-color': $bg-color,
        'color': $text-color
        )
      );
    }
  2. 如果還需要使用!important去覆蓋一些因為權重問題無法應用的樣式(比如使用了外部UI庫,外部UI庫中使用了!important,需要覆蓋該樣式),怎麼解決?

    // 這裡提供一個思路,可以新增一個引數$important
    @mixin themify($key, $valueMap: null, $important: false) {
        // xxx
    }

相關文章