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主题
  [data-theme='dark'] & {
    background-color: $bg-color-dark;
  }
}

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

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

最后,当我们点击不同主题按钮时,就会更新body上的主题标识data-theme,这样,样式文件中对应的主题样式就会被应用上了。

完整代码和实现效果可以参考Codepen:
CodePen: SASS实现主题换肤/主题切换-基础版

不过该实现有点粗糙,存在几个小问题:

  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主题
  [data-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主题
  [data-theme='dark'] & {
    #{$key}: map-get($valueMap, 'dark');
  }
  
  // dark1主题
  [data-theme='dark1'] & {
    #{$key}: map-get($valueMap, 'dark1');
  }
  
  // dark2主题
  [data-theme='dark2'] & {
    #{$key}: map-get($valueMap, 'dark2');
  }
}

当然,我们还可以对mixin做一下优化,可以将主题封装成list格式,然后通过遍历主题,简化mixin:

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

这样看起来就清爽多了。
完整代码和实现效果可以参考Codepen:
CodePen: SASS实现主题换肤/主题切换-进阶版

总结

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
    }
    
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容