Angular Material 18+ 高级教程 – Custom Themes for Material Design 2 (自定义主题 Material 2)
v18 更新重要说明
从 Angular Material v18 开始,默认使用的是 Material 3 Design (简称 M3),本篇教的是旧版本的 Material 2 Design (简称 M2)。
虽然如此,但它们的调用手法和理念大同小异 (有些功能 M2 有,M3 没有),所以我建议大家从本篇学起,然后才去学下一篇 Material 3。
或许 v19 后我会把两篇做合并。
阅读前有 2 个点要知道:
v18 后 CLI 默认创建的是 M3 版本的 demo 代码,而本篇展示的是 M2 版本的 demo 代码。
- 有一些 (不是全部哦) CSS variables 和 mixin 需要加上 $m2 prefix 来表示使用 M2 版本。
像下图这样 (注:本篇 demo 代码没有加上 $m2 是因为本篇是 v17 时期写的,我暂时懒得改了)
前言
我们之所以选择使用 Angular Material 是因为我们喜欢 Material Design,然而,如果每个使用 Angular Material 开发的项目都看起来完全一样,那就不太理想了。
我们希望项目在保持 Material Design 的同时,也能够有一些个性化的差异。
为此,Angular Material 提供了自定义主题 (Custom Theme) 方案,它允许我们微调 Material Design 的风格,使我们的项目变得有点个性。
参考
Docs – Theming Angular Material
Docs – Theme your own components with Angular Material's theming system
Docs – Customizing Angular Material component styles
Material Design 2 Docs – The color system
Theme 可以微调什么?
Theme 可以调三个元素:
颜色 -- Color
对颜色一窍不通的朋友,可以先看这 3 篇文章
字体 -- Typography
对字体一窍不通的朋友,可以先看这篇 -- 平面设计 – 字体
亲密度 -- Density
对排版亲密度一窍不通的朋友,可以先看这篇 -- 平面设计 – 排版四大原则
举例:两个项目,一个用红色作为主题,另一个用蓝色作为主题。
红主题项目中的 Material 组件就是红色的,蓝主题项目就是蓝色的。
Prebuilt Themes
创建 Angular 项目
ng new material --routing=false --ssr=false --skip-tests --style=scss
添加 Angular Material
ng add @angular/material
在添加 Angular Material 过程中它会有几个选项
第一个就是选择 Theme。
总共有 4 个 Prebuilt Themes 可选:indigo-pink,deeppurple-amber,pink-bluegrey,purple-green。
下图是不同主题下的 Button 组件
不同的主题定义了不同的主色 (primary color) 和强调色 (accent color)。
这些 Prebuilt Themes 是通过 angular.json 设置的
Custom Theme Get Started
在添加 Angular Material 时选择 Custom Theme
选择 Custom Theme 后,angular.json 就没用 Prebuilt Themes 了。
取而代之的是 styles.scss 多了许多代码
我们一行一行推敲看看,不用深入,下一 part 会细讲。
首先是执行了一个 core 方法,顾名思义就是输出一些 base styles。
接着是
palette 的意思是调色板,从命名和调用上可以看出来,这里主要是通过调色板生成了一些颜色,然后用变量 primary,accent,warn 装起来。
接着用三个颜色创建一个主题
最后 apply 到所有组件上
步骤总结:调色 -> 配色 -> 创建主题 -> aplly to 组件
Color Theme
上一 part 我们只是过个场,这一 part 来讲细节了。
Palette 調色盤
我们要自定义颜色,首先要搞懂颜色和調色盤。
Material Design 的颜色可不是简简单单的红黄蓝绿,它的颜色是包含明度的,下图是 3 个 Material 颜色 -- 红,粉,紫
每种颜色加入明度后派生出了 14 个 Level。50 - 900 是单纯的明度 Level,A100 的 A stand for Accent,也就是强调色 (不是所有颜色都会有 Accent,但大部分都有)。
Angular Material 有 built-in 所有 Material Design 的颜色 (完整 color list 看这里)
$material-primary: mat.define-palette(mat.$red-palette);
mat.$red-palette,mat.$pink-palette,mat.$purple-palette 就代表了下图三个颜色
Custom Base Palette
如果我们想要的颜色不在 built-in color list 里,我们可以自己创建一个。
通过工具 Tools for picking colors (不使用工具也行,自己拿捏明度 Level 就可以了)
用 Scss declare a Base Palette
$my-base-palette: (
50: #efe6ff,
100: #d5c1fd,
200: #b897fe,
300: #986aff,
400: #7c45fe,
500: #5b16fc,
600: #4b10f6,
700: #3002ee,
800: #0000e9,
900: #0000e5, contrast: (
50: rgba(black, 0.87),
100: rgba(black, 0.87),
200: rgba(black, 0.87),
300: white,
400: white,
500: white,
600: white,
700: white,
800: white,
900: white,
),
);
50 - 900 的颜色来自工具,contrast (对比色) 只能是黑或白,不过黑不是纯黑,而是黑的 0.87%,这个是 Material Design 的规范。
验证 contrast 的工具:WebAIM
Primary,Accent,Warn Color
一个主题能配置 3 个颜色,每一个的用途都不一样:
Primary -- 主色
主色就是主角啦,戏份最多,一眼看过去出现最多的就是主色啦。
Accent -- 强调色
万绿丛中一点红,Accent 通常会用在一些要引起用户交互的地方,比如 Call to Action。
Warn -- 警告色
通常出现在一些 Error 的地方。
Define Primary Palette
上面我们创建的是 Base Palette,它太底层了,Angular Material 无法直接使用,我们还需要经过一轮加工 -- mat.define-palette
$material-primary: mat.define-palette($my-base-palette);
mat.define-palette 的源码在 _theming.scss
主要是扩展了原本的 Base Palette,增加了多几个属性,方便上层使用。比如:
default
默认就是 Base Palette 的 500 Level 明度
lighter
亮一点,默认 100 明度
darker
暗一点,默认 700 明度
还有 text 和一堆颜色对应的 contrast
Define Accent Palette
如果 Base Palette 支持 Accent 的话,比如
在 define Accent Palette 时可以传入参数,自定义 default,lighter,darker 的明度 Level。
$material-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);
Define Warn Palette
$material-warn: mat.define-palette(mat.$red-palette);
Warn Palette 通常就是用 mat.$red-palette。
Define Color Theme
把 Primary,Accent,Warn 3 个颜色传入 mat.define-light-theme 方法,创建出最终的 Theme。
$material-primary: mat.define-palette($my-base-palette);
$material-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);
$material-warn: mat.define-palette(mat.$red-palette); $material-theme: mat.define-light-theme(
(
color: (
primary: $material-primary,
accent: $material-accent,
warn: $material-warn,
),
)
);
这个方法还有一个 dark 版本的 mat.define-light-theme,顾名思义就是暗黑版本。效果是这样的
可以看到不同的主题,Angular Material Card 组件会呈现不同的颜色 (e.g. background-color)
Apply Theme
Apply Theme 给所有组件
@include mat.all-component-themes($material-theme);
如果我们的项目没用使用那么多组件,其实可以不需要 apply to all components。
all-component-themes 源码在 _all-theme.scss
第一句是 core-theme,其它的是每一个组件 theme。我们也可以像它这样
// @include mat.all-component-themes($material-theme);
@include mat.core-theme($material-theme); // for core
@include mat.button-theme($material-theme); // for button
@include mat.card-theme($material-theme); // for card
Dark Mode
上一 part 我们有提到了 Dark Theme,这一 part 我们就来具体实现 Dark Mode 吧。
首先创建多一个 Dark Theme。
@use '@angular/material' as mat; @include mat.core(); $material-primary: mat.define-palette(mat.$indigo-palette);
$material-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);
$material-warn: mat.define-palette(mat.$red-palette); $material-light-theme: mat.define-light-theme((
color: (
primary: $material-primary,
accent: $material-accent,
warn: $material-warn,
)
)); $material-dark-theme: mat.define-dark-theme((
color: (
primary: $material-primary,
accent: $material-accent,
warn: $material-warn,
)
));
Primary,Accent,Warn 颜色是一样的 (当然你要不一样也可以),只是创建 Theme 用了不同的方法,一个用 define-light-theme 另一个用 define-dark-theme。
接着写 media query apply Theme。
@include mat.all-component-themes($material-light-theme); @media (prefers-color-scheme: dark) {
@include mat.all-component-colors($material-dark-theme);
}
注意,media query 里使用的方法是 mat.all-component-colors 而不是 mat.all-component-themes,因为 Theme 除了 Color 还包含 Typography 和 Density。
如果我们使用 mat.all-component-themes 那 Typography 和 Density 就 duplicate styles 了,会报错的。
使用 mat.all-component-colors 就只会 override Color Theme 而已。
Multiple Theme
Dark Mode 其实也算是 Multiple Theme,只是它比较简单,我们来点复杂的 -- 让用户自己能切换主题。
预期效果
有 4 个 Themes,点击 Chip 就可以切换。
首先,我们封装一个简单的 create theme 函数,方便复用
@function create-theme($primary-palette, $accent-palette, $warn-palette, $prefers-color-scheme: 'light') {
$primary: mat.define-palette($primary-palette);
$accent: mat.define-palette($accent-palette, A200, A100, A400);
$warn: mat.define-palette($warn-palette); @if($prefers-color-scheme == 'dark') {
@return mat.define-dark-theme((
color: (
primary: $primary,
accent: $accent,
warn: $warn,
)
));
}
@else {
@return mat.define-light-theme((
color: (
primary: $primary,
accent: $accent,
warn: $warn,
)
));
}
}
然后创建 4 个 Themes。
$theme1: create-theme(mat.$deep-purple-palette, mat.$amber-palette, mat.$red-palette, 'light');
$theme2: create-theme(mat.$indigo-palette, mat.$pink-palette, mat.$red-palette, 'light');
$theme3: create-theme(mat.$pink-palette, mat.$blue-grey-palette, mat.$red-palette, 'dark');
$theme4: create-theme(mat.$purple-palette, mat.$green-palette, mat.$red-palette, 'dark');
接着 apply to 组件
@include mat.all-component-themes($theme1); body.theme2 {
@include mat.all-component-colors($theme2);
}
body.theme3 {
@include mat.all-component-colors($theme3);
}
body.theme4 {
@include mat.all-component-colors($theme4);
}
默认是 theme1,当 body 有 theme class 时就覆盖上其它的 Theme。
App 组件
import { Component } from '@angular/core';
import { MatButtonModule } from '@angular/material/button';
import { MatCardModule } from '@angular/material/card';
import { MatChipsModule } from '@angular/material/chips'; @Component({
selector: 'app-root',
standalone: true,
imports: [MatButtonModule, MatCardModule, MatChipsModule],
templateUrl: './app.component.html',
styleUrl: './app.component.scss'
})
export class AppComponent {
selectedTheme = 'theme1';
themes = ['theme1', 'theme2', 'theme3', 'theme4']; updateTheme(selectedTheme: string) {
document.body.classList.remove(this.selectedTheme);
this.selectedTheme = selectedTheme;
document.body.classList.add(this.selectedTheme);
}
}
切换 Theme 时就修改 body class。
App Template
<mat-chip-listbox>
@for (theme of themes; track theme) {
<mat-chip-option (selectionChange)="updateTheme(theme)" [selected]="theme === selectedTheme">{{ theme }}</mat-chip-option>
}
</mat-chip-listbox> <mat-card>
<mat-card-header>
<mat-card-title>Title</mat-card-title>
<mat-card-subtitle>Subtitle</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<div class="button-list">
<button mat-raised-button color="primary">Primary</button>
<button mat-raised-button color="accent">Accent</button>
<button mat-raised-button color="warn">Warn</button>
<button mat-raised-button disabled>Disabled</button>
</div>
</mat-card-content>
</mat-card>
App 组件和 Template 不是重点,你想如何切换 Theme 都可以,这里只是给一个例子。
Lazy Load Theme (styles.css)
在做 Multiple Theme 时,我们可以通过 Angular CLI 做 split bundle 达到 Lazy Load Theme 的效果,这样可以提升 first page load performance。
首先,创建一个 _styles-theme-shared.scss,内容是上面的 create-theme 函数
@use '@angular/material' as mat; @function create-theme($primary-palette, $accent-palette, $warn-palette, $prefers-color-scheme: 'light') {
$primary: mat.define-palette($primary-palette);
$accent: mat.define-palette($accent-palette, A200, A100, A400);
$warn: mat.define-palette($warn-palette); @if($prefers-color-scheme == 'dark') {
@return mat.define-dark-theme((
color: (
primary: $primary,
accent: $accent,
warn: $warn,
)
));
}
@else {
@return mat.define-light-theme((
color: (
primary: $primary,
accent: $accent,
warn: $warn,
)
));
}
}
然后把 4 个 Themes 拆成 4 个 styles.scss
styles-theme1.scss
@use '@angular/material' as mat;
@use './styles-theme-shared' as shared; @include mat.core(); $theme1: shared.create-theme(mat.$deep-purple-palette, mat.$amber-palette, mat.$red-palette, 'light'); @include mat.all-component-themes($theme1);
styles-theme2.scss
@use '@angular/material' as mat;
@use './styles-theme-shared' as shared; $theme2: shared.create-theme(mat.$indigo-palette, mat.$pink-palette, mat.$red-palette, 'light'); body.theme2 {
@include mat.all-component-colors($theme2);
}
styles-theme3.scss
@use '@angular/material' as mat;
@use './styles-theme-shared' as shared; $theme3: shared.create-theme(mat.$pink-palette, mat.$blue-grey-palette, mat.$red-palette, 'dark'); body.theme3 {
@include mat.all-component-colors($theme3);
}
styles-theme4.scss
@use '@angular/material' as mat;
@use './styles-theme-shared' as shared; $theme4: shared.create-theme(mat.$purple-palette, mat.$green-palette, mat.$red-palette, 'dark'); body.theme4 {
@include mat.all-component-colors($theme4);
}
接着在 angular.json 做配置
"styles": [
"src/styles-theme1.scss",
{
"input": "src/styles-theme2.scss",
"inject": false
},
{
"input": "src/styles-theme3.scss",
"inject": false
},
{
"input": "src/styles-theme4.scss",
"inject": false
}
],
声明 theme2,theme3,theme4 不要放入 index.html。
这样 bundle 出来的结果是
index.html 只有 theme1.scss
theme2.scss,theme3.scss,theme4.scss 会被独立 bundle 出来
接着在 App 组件切换 Theme 时才 append link to load theme CSS file。
export class AppComponent {
selectedTheme = 'theme1';
themes = ['theme1', 'theme2', 'theme3', 'theme4']; updateTheme(selectedTheme: string) {
document.body.classList.remove(this.selectedTheme);
this.selectedTheme = selectedTheme;
document.body.classList.add(this.selectedTheme);
const href = `styles-${selectedTheme}.css`;
const existingLink = document.head.querySelector(`link[href="${href}"]`);
if(existingLink === null) {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = `styles-${selectedTheme}.css`;
document.head.appendChild(link);
}
}
}
Lazy Load Theme (styles.css) for Dark Mode
如果只是做 Dark Mode,我们不需要自己去 append link 这么麻烦。
用 <link> element + [media] attirbute 就可以了
<link rel="stylesheet" href="theme-dark.css" media="(prefers-color-scheme: dark)" />
提醒:即便 media not match,游览器依然会 preload theme-dark.css 的哦。
Medium – Why Browsers Download Stylesheets With Non-Matching Media Queries
Default Typography Theme
Material Design 默认的 font-family 是 Roboto,Type Scale 有 13 个 Level。
不过 Angular Material 对它的理解只有 12 个 Level (少了 OVERLINE,我也不知道为什么)
而最终 Angular Material 只实现了 11 个 Level,外加 2 个不在 Material Design 规范内的 Level
正版的 overline 和 button 没有实现,但是多了两个山寨版的 mat-h5 和 mat-h6,也是醉啦。
mat-h5 和 mat-h6 其实是取之于 body-2,它是缩小版的 body-2。
Use Default Typography Theme
添加 Angular Material 到项目
ng add @angular/material
顺便选一个 Theme (我选了 Indigo/Pink)。
Angular CLI 会替我们在 index.html 引入 Roboto 字体。
在 style.scss 会添加 font-family Roboto
在 App Template 使用 Angular Material 组件 (我拿 Material Card 组件做例子)
<mat-card>
<mat-card-header>
<mat-card-title>Title</mat-card-title>
<mat-card-subtitle>Subtitle</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Consequuntur ullam ex totam iste natus, odtio error delectus!</p>
</mat-card-content>
</mat-card>
效果
组件的 font-size,font-weight 都是依据 Default Typography Theme,也就是 Material Design 规范。
查看最终的 HTML,title 和 subttile 分别使用 class 'mat-mdc-card-title' 和 'mat-mdc-card-title'
查看 card.scss 源码
继续追踪会去到 _card.scss
title 使用的是 Level headline-6 也就是 size 20px,weight 500。
subtitle 使用的是 Level subtitle-2 也就是 size 14px,weight 500。
我们也可以使用这些 Level,在 element 添加 class 就可以了
<h1 class="mat-headline-5">Headline 5</h1>
<h2 class="mat-headline-6">Headline 6</h2>
<h3 class="mat-h5">Mat h5</h3>
<h4 class="mat-h6">Mat h6</h4>
<h5 class="mat-subtitle-1">Subtitle 1</h5>
<h6 class="mat-subtitle-2">Subtitle 2</h6>
效果
注:margin-bottom 也是 Headline 负责的哦,字体越大 margin-bottom 越大。
有时候项目会使用 h1 – h6 element 来管理 font-size,我们只需要添加一个 class mat-typography 到 container (通常是放在 body)
<div class="mat-typography">
<h1>Headline 5</h1>
<h2>Headline 6</h2>
<h3>Subtitle 1</h3>
<h4>Body 1</h4>
</div>
旗下的 h1 - h6 就会对应到不同的 Material Typography Scale Level。
效果
Class Name,Level Name,h1 – h6 的配对是很混乱的,使用的时候要看清楚哦。
有些 Class Name 还有 alias 别名,更乱。
另外,上面这个图是 v15 和以后的版本,下图是 v14 和之前的版本,它们的名字还不一样,就问你晕不晕。
Custom Typography Theme
Angular Material 允许我们修改所有的 Type Scale Level 的尺寸,包括 font-size, font-weight, line-height, letter-spacing, font-family。
Use Default Typography Theme
我们先用 Custom Typography Theme 的方式来使用 Default Typography Theme。
添加 Angular Material 到项目
ng add @angular/material
选择 Custom Theme
App Template
<mat-card>
<mat-card-header>
<mat-card-title>Title</mat-card-title>
<mat-card-subtitle>Subtitle</mat-card-subtitle>
</mat-card-header>
<mat-card-content>
<p>Lorem, ipsum dolor sit amet consectetur adipisicing elit. Consequuntur ullam ex totam iste natus, odtio error delectus!</p>
</mat-card-content>
</mat-card>
效果
由于我们选了 Custom Theme,那原本 Default Typography Theme 的 font-size 和 font-weight 就没了。
我们手动把 Default Typography Theme 添加回去,在 styles.scss 添加
@include mat.core(); $material-primary: mat.define-palette(mat.$indigo-palette);
$material-accent: mat.define-palette(mat.$pink-palette, A200, A100, A400);
$material-warn: mat.define-palette(mat.$red-palette); // 1. 创建 Default Typography
$default-typography: mat.define-typography-config();
$material-theme: mat.define-light-theme((
color: (
primary: $material-primary,
accent: $material-accent,
warn: $material-warn,
),
// 2. 把 Typography 添加到 Custom Theme
typography: $default-typography
)); @include mat.all-component-themes($material-theme);
效果
虽然 Angular Material 组件有 Default Typography Theme 了,但仅限于 Angular Material 组件,我们自己还用不到这个 Default Typography Theme。
<h1 class="mat-headline-5">Headline 5</h1>
<h2 class="mat-headline-6">Headline 6</h2>
<h3 class="mat-h5">Mat h5</h3>
<h4 class="mat-h6">Mat h6</h4>
<h5 class="mat-subtitle-1">Subtitle 1</h5>
<h6 class="mat-subtitle-2">Subtitle 2</h6>
效果
我们需要在 styles.scss 添加这句
效果
Create Custom Typography Theme
首先我们要定义每一个 Type Scale Level 的尺寸。
$my-headline-1--level: mat.define-typography-level(
$font-weight: 700,
$font-size: 48px,
$line-height: 56px
); $my-headline-2--level: mat.define-typography-level(
$font-weight: 700,
$font-size: 32px,
$line-height: 40px
);
mat.define-typography-level 方法的源码在 _definition.scss
Material Design 完整 13 个 Level 都可以 set,不过 button 和 overline 是没有对应的 class 的哦。(有 class mat-headline-1,但是没有 mat-button 和 mat-overline)
另外,class mat-h5 等于 body-2 缩写 0.83,class mat-h6 等于 body-2 缩小 0.67。
接着把 Custom Level 放入 Typography Config
$my-custom-typography-config: mat.define-typography-config(
$headline-1: $my-headline-1--level,
$headline-2: $my-headline-2--level
);
没有 override 的它会用 default。
然后 set to theme 和 typography hierarchy。
效果
<h1 class="mat-headline-1">Headline 1</h1>
<h2 class="mat-headline-2">Headline 2</h2>
如果要换 font-family 可以直接换 index.html 的 <link> 和 styles.scss 的 body styles。
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Open+Sans:ital,wght@0,300..800;1,300..800&display=swap" rel="stylesheet">
body { margin: 0; font-family: "Open Sans", sans-serif; }
然后在 define-typography-level 时设置 $font-family
$my-headline-1--level: mat.define-typography-level(
$font-family: 'Open Sans'
$font-weight: 700,
$font-size: 48px,
$line-height: 56px,
);
效果
Density Theme
Density 是密度的意思,这里的密度指的是排版四大原则之一的亲密度 -- 元素之间的间距。
下面 button 的高度是 36px
如果我们想让它密集一点,可以通过调整它的 Density。
在 define-light-theme 传入 density 值。
$theme: mat.define-light-theme((
color: (
primary: $primary,
accent: $accent,
warn: $warn,
),
typography: $typography,
density: -1
));
0 代表 normal,-1 代表更密集 (通常是 4px),-2 就 8px 以此类推
效果
注:density 最小是 -3,因为 density 只能减少空间 (spacing),字体是不缩小的。
如果想单独控制一个区域也可以
.density-1 {
$density-1-theme: mat.define-light-theme((
density: -1
));
@include mat.all-component-densities($density-1-theme);
}
把 density -1 apply under class density-1。
注:和 Color Theme 一样,这里用的是 mat.all-component-densities 而不是 mat.all-component-themes,
因为 all-component-themes 包含 Color 和 Typography,apply 2 次会导致 duplicated,而 all-component-densities 只覆盖 Density。
然后 container 加上 class density-1 就可以了
<div class="button-list density-1">
<button mat-raised-button color="primary">Primary</button>
<button mat-raised-button color="accent">Accent</button>
<button mat-raised-button color="warn">Warn</button>
</div>
How Theme Work?
要了解 Theme 如何工作,最彻底的方式自然是逛源码咯。
但是 Scss 源码可不好阅读,因此我们还是看看 ng build 出来的 CSS,然后做点推敲就好。
首先,关闭 ng build 的 Uglify 功能,不然乱码我们读不了。
接着关闭 bundle size error,因为不 Uglify 文件会变大很多。
Base Theme
styles.scss
@use '@angular/material' as mat; // 1. base styles 我们就不研究了,看组件就好了
// @include mat.core(); $base-theme: mat.define-light-theme(());
@include mat.button-base($base-theme);
mat.button-base 输出的是 Material Button 组件的 base styles,它和 Color、Typography、Density Theme 无关,
虽然如此,但我们依然需要传入一个 $theme,因为 $theme 里面还有一些小属性是它会用到的,比如 version。
styles.css (after ng build)
@include mat.button-base($base-theme) 最终输出的是一堆 CSS Variables。
Angular Material 的所有组件都是靠 CSS Variables 来做调整的,倘若没有对应的 CSS Variables 那就表示这个设计不能调整。
当然如果我们硬硬要改的话,还是可以使用 ::ng-deep 这类黑科技来达到目的。对魔改感兴趣的朋友建议看看这篇:Docs – Customizing Angular Material component styles
好,回到主题
--mdc-text-button-container-shape: 4px;
--mat-text-button-horizontal-padding: 8px;
--mat-text-button-icon-spacing: 8px;
从上面几个例子可以看出它的命名规范,我们自定义的属性也可以依据这个命名规范,比如
--my-component-container-shape: 4px;
--my-component-horizontal-padding: 8px;
--my-component-icon-spacing: 8px;
Color Theme
styles.scss
$primary-color: mat.define-palette(mat.$indigo-palette);
$accent-color: mat.define-palette(mat.$pink-palette, A200, A100, A400);
$warn-color: mat.define-palette(mat.$red-palette);
$color-theme: mat.define-light-theme((
color: (
primary: $primary-color,
accent: $accent-color,
warn: $warn-color,
),
));
@include mat.button-color($color-theme);
每个 Angular Material 组件都有 base,color,typography,density 四个方法。
上一 part 的 mat.button-base 输出 Button 组件 base styles CSS Variables,这里的 mat.button-color 则输出和 Color 相关的 CSS Variables。
styles.css
全部都是和 Color 相关的 CSS Variables。
不同主题色是通过 class mat-primary,mat-accent,mat-warn 覆盖 base CSS Variables 来实现的。
Typography Theme
styles.scss
$typography-config: mat.define-typography-config();
$typography-theme: mat.define-light-theme((
typography: $typography-config,
));
@include mat.button-typography($typography-theme);
styles.css
全部都是和 Typography 相关的 CSS Variables。
Density Theme
styles.scss
$density-theme: mat.define-light-theme((
density: -3,
));
@include mat.button-density($density-theme);
style.css
上面 density 是 -3,build 出来的 CSS Variables 是 24px,如果 density 是 0 那 build 出来的是 36px。
Angular Material 组件使用 Theme CSS Variables
我们看看 Button 组件如何使用这些 CSS Variables 的。
App Template
main.js (after ng build)
没什么特别的,就是直接拿来用就对了。
总结
每个 Angular Material 组件统一提供了 base,color,typography,density 方法。
我们在 styles.scss 创建 $theme,然后调用这些方法并传入 $theme,这些方法内部会读取 $theme 配置然后输出最终的 CSS Variables。
比如说 mat.button-density($density-theme) 方法内部会读取 $density-theme 中的 density -3 然后输出 CSS Variables 24px。
Custom component reads theme information
上一 part 我们说到
每个 Angular Material 组件统一提供了 base,color,typography,density 方法。
比如 _button-theme.scss (每个组件都有一个 _component-theme.scss 文件)
这些方法要从 $theme 中读取信息并且输出 CSS Variables 到 :root 或者 html,接着组件就可以读取这些 CSS Variables 了。
这 part 我们来看如何从 $theme 中读取相关信息。
Reads color theme information
首先创建一个 Hello World 组件
ng g c hello-world
HelloWorld Template
<h1>Hello World</h1>
HelloWorld Styles
h1 {
background-color: var(--hello-world-title-bg-color);
color: var(--hello-world-title-color); padding: 12px 16px;
width: max-content;
}
引用了 2 个 CSS Variables
创建 _hello-world-theme.scss,里头有一个 color 方法,这个方法是给 styles.scss 调用的。
@mixin color($theme) {
:root {
--hello-world-title-bg-color: red;
--hello-world-title-color: white;
}
}
颜色暂时放 hardcode,待会我们才 read from $theme,先看完整体。
styles.scss
$primary-color: mat.define-palette(mat.$indigo-palette);
$accent-color: mat.define-palette(mat.$pink-palette, A200, A100, A400);
$warn-color: mat.define-palette(mat.$red-palette);
$color-theme: mat.define-light-theme((
color: (
primary: $primary-color,
accent: $accent-color,
warn: $warn-color,
),
));
@include hello-world.color($color-theme);
效果
read primary color from theme
@use '@angular/material' as mat; @mixin color($theme) { $primary: mat.get-theme-color($theme, primary, default);
$primary-contrast: mat.get-theme-color($theme, primary, default-contrast); :root {
--hello-world-title-bg-color: #{$primary};
--hello-world-title-color: #{$primary-contrast};
}
}
通过 mat.get-theme-color 方法读取 primary color
效果
mat.get-theme-color 有 3 个参数:
theme
palette name
有 5 个选项 primary,accent,warn,background,foreground
color name
不同 palette name 有不同的 color name 选项
下图是 primary,accent,warn 的 color name 选项
上面例子就是用了 primary 配 default 和 default-contrast。下图是 background 可选的 color name
比如:那 dialog background 将得到白色$dialog-bg-color: mat.get-theme-color($theme, background, dialog); // white
下图是 foreground 可选的 color name
除了拿各种颜色,还可以拿 prefers-color-scheme,看是 dark theme 还是 light theme。
$prefers-color-scheme: mat.get-theme-type($theme); // dark | light
$background: if($prefers-color-scheme == dark, black, white);
Reads typography theme information
在 HelloWorld Styles 添加 font-size
styles.scss
$typography-config: mat.define-typography-config();
$typography-theme: mat.define-light-theme((
typography: $typography-config,
));
@include hello-world.typography($typography-theme);
_hello-world-theme.scss
@mixin typography($theme) {
$headline-1-font-size: mat.get-theme-typography($theme, headline-1, font-size); :root {
--hello-world-title-font-size: #{$headline-1-font-size};
}
}
mat.get-theme-typography 方法可以获取所有 Typography Scale Level 的 information,它有两个参数
Level Name
Property Name 选项有 font-size, font-weight, line-height, letter-spacing, font-family
如果没有指定参数两 Property Name 它会返回 font shorthand,比如
$headline-1-font-size: mat.get-theme-typography($theme, headline-1); // 300 96px / 96px Roboto, sans-serif
Reads density theme information
Density 和 Color, Typography 一样我就不重复写了。
最关键的是
$density: mat.get-theme-density($theme);
mat.get-theme-density 方法会返回一个数,从 0 到 -5。
依据 Material 规范每 -1 Level 密度就要靠近 4px。
大概长这样
@mixin density($theme) {
$density: mat.get-theme-density($theme);
$default-height: 36px;
$height-after-density: calc($default-height - ($density * -1 * 4px)); :root {
--hello-world-container-height: #{$height-after-density};
}
}
Check has Theme
下面这个是完整的 Theme
$theme: mat.define-light-theme((
color: (
primary: $primary-color,
accent: $accent-color,
warn: $warn-color,
),
typography: $typography-config,
density: -1
));
下面这个是只有 Density 的 Theme
$density-theme: mat.define-light-theme((
density: -1,
));
它们都调用 define-light-theme 方法,只是传入的配置可能不完整。
当我们在使用 Theme 时如果有可能出现不完整,那最好检查一下才使用
$has-base: mat.theme-has($theme, base);
$has-color: mat.theme-has($theme, color);
$has-typography: mat.theme-has($theme, typography);
$has-density: mat.theme-has($theme, density);
目录
上一篇 Angular Material 18+ 高级教程 – Get Started
下一篇 Angular Material 18+ 高级教程 – Custom Themes for Material Design 3 (自定义主题 Material 3)
想查看目录,请移步 Angular 18+ 高级教程 – 目录
喜欢请点推荐,若发现教程内容以新版脱节请评论通知我。happy coding
Angular Material 18+ 高级教程 – Custom Themes for Material Design 2 (自定义主题 Material 2)的更多相关文章
- Siki_Unity_2-9_C#高级教程(未完)
Unity 2-9 C#高级教程 任务1:字符串和正则表达式任务1-1&1-2:字符串类string System.String类(string为别名) 注:string创建的字符串是不可变的 ...
- Pandas之:Pandas高级教程以铁达尼号真实数据为例
Pandas之:Pandas高级教程以铁达尼号真实数据为例 目录 简介 读写文件 DF的选择 选择列数据 选择行数据 同时选择行和列 使用plots作图 使用现有的列创建新的列 进行统计 DF重组 简 ...
- ios cocopods 安装使用及高级教程
CocoaPods简介 每种语言发展到一个阶段,就会出现相应的依赖管理工具,例如Java语言的Maven,nodejs的npm.随着iOS开发者的增多,业界也出现了为iOS程序提供依赖管理的工具,它的 ...
- 【读书笔记】.Net并行编程高级教程(二)-- 任务并行
前面一篇提到例子都是数据并行,但这并不是并行化的唯一形式,在.Net4之前,必须要创建多个线程或者线程池来利用多核技术.现在只需要使用新的Task实例就可以通过更简单的代码解决命令式任务并行问题. 1 ...
- 【读书笔记】.Net并行编程高级教程--Parallel
一直觉得自己对并发了解不够深入,特别是看了<代码整洁之道>觉得自己有必要好好学学并发编程,因为性能也是衡量代码整洁的一大标准.而且在<失控>这本书中也多次提到并发,不管是计算机 ...
- 分享25个新鲜出炉的 Photoshop 高级教程
网络上众多优秀的 Photoshop 实例教程是提高 Photoshop 技能的最佳学习途径.今天,我向大家分享25个新鲜出炉的 Photoshop 高级教程,提高你的设计技巧,制作时尚的图片效果.这 ...
- 展讯NAND Flash高级教程【转】
转自:http://wenku.baidu.com/view/d236e6727fd5360cba1adb9e.html 展讯NAND Flash高级教程
- Net并行编程高级教程--Parallel
Net并行编程高级教程--Parallel 一直觉得自己对并发了解不够深入,特别是看了<代码整洁之道>觉得自己有必要好好学学并发编程,因为性能也是衡量代码整洁的一大标准.而且在<失控 ...
- [转帖]tar高级教程:增量备份、定时备份、网络备份
tar高级教程:增量备份.定时备份.网络备份 作者: lesca 分类: Tutorials, Ubuntu 发布时间: 2012-03-01 11:42 ė浏览 27,065 次 61条评论 一.概 ...
- Django 2.0.1 官方文档翻译: 高级教程:如何编写可重用的app (page 13)
高级教程:如何编写可重用的app (page 13) 本节教程上接第七部分(Page 12).我们会把我们的 web-poll应用转换成一个独立的python包,你可以在新的项目中重用或者把它分享给其 ...
随机推荐
- Java-文件下载案例
文件下载需求 1.页面显示超链接 2.点击超链接后弹出下载框 3.完成图片文件下载 分析 1.超链接指向的资源如果能够被浏览器解析,则在浏览器中展示,如果不能解析,则弹出下载提示框.不满足需求 2.任 ...
- C#委托的2种调用方式
第一种:直接调用,通过invoke方法: 第二种:这是第二种将委托作为方法的参数的间接调用: 下面举个栗子演示: using System; using System.Collections.Gene ...
- 只会建数据库怎么写API?database2api 能帮到你!
database2api 意为 DataBase to API,即只要有数据库,就可以生成开放 API. database2api 是一款强大而便捷的工具,主要功能是依据现有的数据库自动生成开放的 A ...
- Visual Studio 必备插件集合:AI 助力开发
一.前言 2024年AI浪潮席卷全球,编程界迎来全新的挑战与机遇.智能编程.自动化测试.代码审查,这一切都得益于AI技术的迅猛发展,它正在重塑开发者的日常,让编写代码变得更加高效.智能. 精选出最受 ...
- Django Template层之Template概述
Django Template层之Template概述 by:授客 QQ:1033553122 实践环境 Python版本:python-3.4.0.amd64 下载地址:https://www.py ...
- Fiddler 使用fiddler无法抓取苹果手机https请求问题解决方案
使用fiddler无法抓取苹果手机https请求问题解决方案 by:授客 QQ:1033553122 测试环境 Win10 Fiddle4 IPhone6s 问题描述 使用fiddler抓取IPh ...
- Linux中&&、&、|、||等特殊符号
&& 和 & & 表示任务后台执行,与nohup命令功能差不多. # 运行jar包,并且置于后台执行,执行的日志重定向到当前默认的log.txt文件中 [root@lo ...
- docker 网络互通
自定义网络 查看所有的docker网络 网络模式 [root@docker ~]# docker network ls NETWORK ID NAME DRIVER SCOPE a4d70d5796e ...
- 【Vue】图片裁剪功能支持
一.效果展示: 1.表单的图片上传项: - 新增时默认一个空白Input框 - 更新时展示以往上传存放的图片, - 点击[查看]浏览完整大小 - 点击[删除]清空src地址,重新上传新照片 2.裁剪框 ...
- 【FastDFS】04 Docker搭建
直接拉取镜像,创建容器并运行容器一把梭哈: docker run -d --restart=always \ --privileged=true \ --net=host \ --name=fastd ...