上一篇介绍了如何自定义 Material 主题,现在来看看怎么定义多个主题,并在运行时动态切换。
可以采用官网介绍的类名包裹方式,或者我们也可以预先编译,按需引入。话不多说,let's rock the code!😎
按类名切换明暗风格
如上篇所言,我们首先就来实现怎么切换网站的暗黑主题。
注意✨:可以在 styles.scss 中调用 angular-material-theme
mixin 多次,来生成多套不同的主题,但 @mat-core
始终只能调用一次。
创建两个主题
咱们先创建两个主题,一个明亮风格的主题作为默认,另一个暗黑风格的主题放在 .unicorn-dark-theme
这个类名中。这样一来,任何在该类名内的元素,都会应用暗黑风格的主题样式。
@import '~@angular/material/theming';
@include mat-core();
// 定义一个自定义颜色配置 (和前一篇一样)
$candy-app-primary: mat-palette($mat-indigo);
$candy-app-accent: mat-palette($mat-pink, A200, A100, A400);
$candy-app-theme: mat-light-theme((
color: (
primary: $candy-app-primary,
accent: $candy-app-accent,
)
));
// 包含默认主题颜色以及文字排版
@include angular-material-theme($candy-app-theme);
// 定义另一个暗黑风格的主题
$dark-theme: mat-dark-theme((
color: (
primary: $candy-app-primary,
accent: $candy-app-accent,
)
));
// 用一个类名包裹暗黑主题样式
.unicorn-dark-theme {
@include angular-material-color($dark-theme);
}
注意✨:暗黑主题采用 angular-material-color
,而不是默认主题使用的 angular-material-theme
,因为后者不光会生成主题颜色的样式,还会生成其他样式。这里只需要改变颜色即可。而如果需要单独改变文字排版,可以使用 angular-material-typography
。 类似的,细粒度的自定义某组件也是同理(例如:mat-button-theme
/ mat-button-color
/ mat-button-typography
)。
额外处理基于覆盖层的组件
因为基于覆盖层的组件(例如:menu、select、dialog 等)不是被应用程序的根组件包裹,而是和根组件平级的 <div class="cdk-overlay-container">
节点。因此,要让它们也能根据类名切换主题样式,需要做一点额外的处理:
import {OverlayContainer} from '@angular/cdk/overlay';
@Component({
// ...
})
export class AppComponent {
// 用以绑定类名
isDarkMode = false;
constructor(private overlayContainer: OverlayContainer) {}
private processOverlayBaseComponentTheme(checked: boolean) {
// 获取这个 div 元素
const overlayContainerElement = this.overlayContainer.getContainerElement()
const themeWrapperClassName = 'unicorn-dark-theme'
if (checked) {
overlayContainerElement.classList.add(themeWrapperClassName);
} else {
overlayContainerElement.classList.remove(themeWrapperClassName);
}
}
}
动态切换类名
通过 Angular 类名绑定,来切换不同的主题:
<div [class.unicorn-dark-theme]="isDarkMode">
<!-- 受主题切换影响的元素和组件 -->
</div>
现在,就可以在两个主题之间自由切换了!😋
看看效果:
多整几个
虽然上面我们实现了动态主题切换,不过只有暗黑/明亮的两个主题反复横跳显得不太够看。所以,现在到了整活儿时间,一次性整它十个颜色!
创建多个主题
相比创建两个主题时手动去创建,创建一个多个主题的 styles.scss,咱们就利用 Sass 的循环来实现,主要分为几步:
自定义主题的开头操作都一样,先引入
mat-core
,并先定义一个默认主题:@import '~@angular/material/theming'; @include mat-core(); // 默认主题颜色配置 $candy-app-primary: mat-palette($mat-indigo); $candy-app-accent: mat-palette($mat-pink, A200, A100, A400); $candy-app-theme: mat-light-theme(( color: ( primary: $candy-app-primary, accent: $candy-app-accent, ) )); // 生成默认主题样式 (颜色,文字排版,间距) @include angular-material-theme($candy-app-theme); @include app-component-color($candy-app-theme); @include theme-test-color($candy-app-theme);
创建一个 map 对象,在这个 map 中定义各个主题配置。每个主题配置的
key
就是这个主题的 class 类名:$app-themes: ( indigo-pink : (primary-base: $mat-indigo, accent-base: $mat-pink), deeppurple-amber: (primary-base: $mat-deep-purple, accent-base: $mat-amber), blue-yellow : (primary-base: $mat-blue, accent-base: $mat-yellow), pink-bluegrey : (primary-base: $mat-pink, accent-base: $mat-blue-gray, ), purple-green : (primary-base: $mat-purple, accent-base: $mat-green), );
通过
@each
循环,创建明暗个五个主题颜色:@each $css-class, $theme in $app-themes { $primary: mat-palette(map-get($theme, primary-base)) $accent: mat-palette(map-get($theme, accent-base)) // key 值作为类名 .#{$css-class} { $light-theme: mat-dark-theme(( color: ( primary: $primary, accent: $accent, ) )); // 生成颜色样式 @include angular-material-color($light-theme); // 添加两个业务组件测试主题适配 @include app-component-color($light-theme); @include theme-test-color($light-theme); } // key 值作为类名 .#{$css-class}-dark { $dark-theme: mat-dark-theme(( color: ( primary: $primary, accent: $accent, ) )); // 生成颜色样式 @include angular-material-color($dark-theme); // 添加两个业务组件测试主题适配 @include app-component-color($dark-theme); @include theme-test-color($dark-theme); } }
动态切换主题
通过改变 root 元素的 class,来动态切换域名:
<div [class]="currentTheme">
<!-- 受主题切换影响的元素和组件 -->
</div>
themelist = [
"amber-lime",
"amber-lime-dark",
"deeppurple-amber",
"deeppurple-amber-dark",
"blue-yellow",
"blue-yellow-dark",
"pink-bluegrey",
"pink-bluegrey-dark",
"purple-green",
"purple-green-dark"
]
switchTheme(theme: string) {
if (this.currentTheme)
this.overlayContainerEle.classList.remove(this.currentTheme);
this.currentTheme = theme
this.overlayContainerEle.classList.add(this.currentTheme);
}
ok,现在我们有了 10 套主题!
不过可以看到,现在编译打包后的 styles.css 和 styles.js,未压缩的情况下,快高达 1MB 了(每个主题会增加 css 文件 50kb 大小)。所以,咱们试着懒加载它们。
懒加载动态切换
现在咱们已经可以动态的切换主题了,可只有两三个主题还好,如果我的应用程序,要提供很多的主题搭配,让用户自由选用呢,styles.scss 一次包含这么多份主题样式,实在有损加载性能(每个主题增加 50kb)。
遇到这种情况,我们可以预先编译好需要用到主题,在运行时动态切换它们,就可以按需加载了。
首先在 src > theme 文件夹中创建几个主题文件,例如这个 green-amber-dark.scss:
@import "~@angular/material/theming"; @import "../app/app.component.theme.scss"; @import "../app/component/theme-test/theme-test.component.theme"; $green-primary: mat-palette($mat-green); $amber-accent: mat-palette($mat-amber); $green-amber-dark-theme: mat-dark-theme(( color: ( primary: $green-primary, accent: $amber-accent ) )); @include angular-material-theme($green-amber-dark-theme); // 添加两个业务组件测试主题适配 @include app-component-color($green-amber-dark-theme); @include theme-test-color($green-amber-dark-theme);
然后,在 angular.json > projects > architect > build > styles 中,新增刚刚创建的主题文件,inject 设置为 false,意味着它只会被打包为单独的 css 文件,但不会被引入项目中:
"styles": [ "src/styles.scss", { "input": "src/theme/green-amber-theme.scss", "inject": false } ],
最后,通过给 <link> 动态赋值即可:
switchTheme(theme: string): void { const id = 'lazy-load-theme'; const link = document.getElementById(id); // 第一次切换,新建一个 link 元素 if (!link) { const linkEl = document.createElement('link'); linkEl.setAttribute('rel', 'stylesheet'); linkEl.setAttribute('type', 'text/css'); linkEl.setAttribute('id', id); linkEl.setAttribute('href', `${theme}-theme.css`); document.head.appendChild(linkEl); } else { // 替换 link 元素的 href 地址 (link as HTMLLinkElement).href = `${theme}-theme.css`; } }
这样就实现了主题文件的懒加载,不过需要注意的是,默认的主题文件依旧需要在 styles.scss 中引用,它也懒加载的话,会导致初次加载时页面闪烁。
小结
本篇介绍了如何动态切换主题,渐进的包括了三种情况:
- 网站只需要在明亮和暗黑模式下切换主题,那么只需要通过一个独特的类名包裹主题即可,切换时只是切换类名即可
- 网站若是需要预设多个主题,可以利用 Sass 的 @each 方法,来批量生成,再根据类名动态切换。
- 如果网站的主题量很大或者可预见的会不断增加,又或者想要尽量多的优化加载性能,可以采用动态加载主题文件的方式,动态的控制一个 link 标签即可。
本篇的三种情况,可以参照 Github 示例,按 branch 切换
效果可以参考 效果展示,点击右上角工具栏可以切换各个主题。
有时候项目里可不止用上了 Material 的组件库,适配第三方组件库的主题,不像自己的业务组件,可以随意更改,就要另辟蹊径了,关于 Material 主题系统的工程实践,咱们下一篇见!🤠
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。