Angular 18.2.11自定义M3主题实现明暗模式切换方案求助
适配Angular 18.2.11的自定义M3明暗主题切换方案
问题背景
使用Angular 18.2.11 + Angular Material + 自定义M3主题,已在theme-consumer.scss中定义明暗主题,但styles.scss固定加载浅色主题,尝试通过@media (prefers-color-scheme: dark)应用暗色主题时触发重复定义错误,需要实现适配系统明暗模式偏好+支持手动切换自定义调色板的方案。
解决方案步骤
1. 完善theme-consumer.scss主题定义
确保明暗主题使用自定义调色板,且变量可被外部访问:
// theme-consumer.scss @use 'sass:map'; @use '@angular/material' as mat; // 自定义调色板(保留你生成的完整内容) $_palettes: ( primary: ( 0: #000000, 10: #001e2e, 20: #06344a, // ... 剩余调色板值 ), tertiary: ( // ... 你的 tertiary 调色板值 ) ); // 合并默认对比度值(保持原有逻辑) $_rest: ( contrast: ( 0: #ffffff, 10: #ffffff, // ... 对应contrast值 ) ); $_primary: map.merge(map.get($_palettes, primary), $_rest); $_tertiary: map.merge(map.get($_palettes, tertiary), $_rest); // 定义明暗主题,使用!global让外部可引用 $light-theme: mat.define-theme(( color: ( theme-type: light, primary: $_primary, tertiary: $_tertiary, ), )) !global; $dark-theme: mat.define-theme(( color: ( theme-type: dark, primary: $_primary, tertiary: $_tertiary, ), )) !global;
2. 修改styles.scss:适配系统偏好+支持手动切换
移除重复的主题定义,通过:root和类选择器分别绑定明暗主题,同时通过媒体查询自动适配系统偏好:
// styles.scss @use '@angular/material' as mat; @use './common.scss'; @use './theme-consumer.scss'; @import "bootstrap/dist/css/bootstrap.min.css"; @import "@fortawesome/fontawesome-free/css/all.min.css"; // 仅调用一次mat.core() @include mat.core(); // 1. 默认加载浅色主题 :root { @include mat.all-component-themes($light-theme); } // 2. 手动切换暗色主题时的类样式 .dark-theme { @include mat.all-component-themes($dark-theme); } // 3. 适配系统暗色模式偏好,仅在未手动设置主题时生效 @media (prefers-color-scheme: dark) { :root:not(.light-theme) { @include mat.all-component-themes($dark-theme); } } // 自定义组件样式,跟随主题切换自动适配 .tertiary-radio { @include mat.radio-color($light-theme, $color-variant: tertiary); .dark-theme & { @include mat.radio-color($dark-theme, $color-variant: tertiary); } } // 应用排版样式,跟随主题切换 @include mat.typography-hierarchy($light-theme); .dark-theme { @include mat.typography-hierarchy($dark-theme); }
3. 添加Angular组件内的手动切换逻辑
创建主题服务控制html元素类名,同时保存用户偏好:
// theme.service.ts import { Injectable, Renderer2, RendererFactory2 } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class ThemeService { private renderer: Renderer2; private htmlElement: HTMLElement; constructor(rendererFactory: RendererFactory2) { this.renderer = rendererFactory.createRenderer(null, null); this.htmlElement = document.documentElement; // 初始化:优先读取本地存储,其次适配系统偏好 const savedTheme = localStorage.getItem('theme'); if (savedTheme) { this.setTheme(savedTheme as 'light' | 'dark'); } else if (window.matchMedia('(prefers-color-scheme: dark)').matches) { this.setTheme('dark'); } } setTheme(theme: 'light' | 'dark') { this.htmlElement.classList.remove('light-theme', 'dark-theme'); this.htmlElement.classList.add(`${theme}-theme`); localStorage.setItem('theme', theme); } toggleTheme() { const currentTheme = this.htmlElement.classList.contains('dark-theme') ? 'light' : 'dark'; this.setTheme(currentTheme); } }
在组件中调用切换逻辑:
// app.component.ts import { Component } from '@angular/core'; import { ThemeService } from './theme.service'; @Component({ selector: 'app-root', template: ` <button (click)="themeService.toggleTheme()">切换明暗主题</button> <!-- 其他组件内容 --> ` }) export class AppComponent { constructor(public themeService: ThemeService) {} }
关键说明
- 用
!global导出主题变量,确保styles.scss能正确引用 - 通过
:root和类选择器避免重复定义错误,媒体查询仅在未手动设置主题时生效 - 本地存储保存用户主题偏好,优先级高于系统默认设置
内容的提问来源于stack exchange,提问作者parascus
相关产品推荐
相关产品推荐

