Angular 16+Angular Material组件级明暗主题切换实现求助
Angular明暗主题切换实现方案
现有代码问题说明
- 当前
styles.scss依赖系统偏好自动切换主题,无法响应手动切换操作 app.component未正确接收并处理header组件发射的主题切换事件- 主题状态未持久化,刷新页面会重置
1. 调整styles.scss:改用类控制主题切换
将原有的媒体查询自动切换逻辑,改为基于body类名的主题应用,这样可以通过添加/移除类来手动控制主题:
@use '@angular/material' as mat; @include mat.core(); // 定义深色主题 $dark-theme: mat.define-dark-theme(( color: ( primary: mat.define-palette(mat.$pink-palette), accent: mat.define-palette(mat.$blue-grey-palette), ), typography: mat.define-typography-config(), density: 0, )); // 定义浅色主题 $light-theme: mat.define-light-theme(( color: ( primary: mat.define-palette(mat.$indigo-palette), accent: mat.define-palette(mat.$pink-palette), ), )); // 全局基础样式 * { box-sizing: border-box; } html, body { height: 100%; // 添加主题切换过渡动画 transition: background-color 0.3s ease, color 0.3s ease; } body { margin: 0; padding: 0; app-root { display: flex; flex-direction: column; min-height: 100vh; } } // 浅色主题样式集合 body.light-theme { @include mat.core-theme($light-theme); @include mat.button-theme($light-theme); @include mat.toolbar-theme($light-theme); @include mat.slide-toggle-theme($light-theme); // 可根据需求添加其他Material组件主题 } // 深色主题样式集合 body.dark-theme { @include mat.core-theme($dark-theme); @include mat.button-theme($dark-theme); @include mat.toolbar-theme($dark-theme); @include mat.slide-toggle-theme($dark-theme); } // 默认主题:优先匹配系统偏好 @media (prefers-color-scheme: light) { body { @extend .light-theme; } } @media (prefers-color-scheme: dark), (prefers-color-scheme: no-preference) { body { @extend .dark-theme; } }
2. 修正app.component.ts:处理主题切换与持久化
接收header的主题切换事件,更新DOM类名,并将用户选择保存到本地存储:
import {Component, OnInit} from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.scss'] }) export class AppComponent implements OnInit { title = 'web'; activeTheme: boolean; // true=浅色主题,false=深色主题 ngOnInit(): void { // 初始化主题:优先读取本地存储的用户选择,否则匹配系统偏好 const savedTheme = localStorage.getItem('activeTheme'); if (savedTheme) { this.activeTheme = savedTheme === 'true'; this.applyTheme(this.activeTheme); } else { const isLightPreferred = window.matchMedia('(prefers-color-scheme: light)').matches; this.activeTheme = isLightPreferred; this.applyTheme(this.activeTheme); } } onThemeChange(themeValue: boolean): void { this.activeTheme = themeValue; this.applyTheme(themeValue); localStorage.setItem('activeTheme', themeValue.toString()); } private applyTheme(isLight: boolean): void { const body = document.body; if (isLight) { body.classList.remove('dark-theme'); body.classList.add('light-theme'); } else { body.classList.remove('light-theme'); body.classList.add('dark-theme'); } } }
3. 修正app.component.html:正确绑定事件
将原有的事件绑定改为接收事件参数的形式:
<header id="header"> <app-header (changeTheme)="onThemeChange($event)" [checked]="activeTheme"></app-header> </header> <main id="main"> <router-outlet></router-outlet> </main> <footer id="footer"> <app-footer></app-footer> </footer>
4. 同步header组件状态
让开关状态与当前主题保持一致,修改header.component.ts添加输入属性:
import {AfterViewInit, Component, ElementRef, EventEmitter, Input, Output, ViewChild} from '@angular/core'; @Component({ selector: 'app-header', styleUrls: ['./header.component.scss'], templateUrl: 'header.component.html' }) export class HeaderComponent implements AfterViewInit { sun = 'light-icon'; // 替换为你的SVG路径 moon = 'dark-icon'; // 替换为你的SVG路径 brand: string = 'MyApp'; @Input() checked: boolean = false; // 接收父组件的主题状态 @Output() changeTheme = new EventEmitter<boolean>(); @ViewChild('darkModeSwitch', {read: ElementRef}) element: ElementRef | undefined ngAfterViewInit(): void { if (this.element) { this.element.nativeElement.querySelector('.mdc-switch__icon--on').firstChild.setAttribute('d', this.moon); this.element.nativeElement.querySelector('.mdc-switch__icon--off').firstChild.setAttribute('d', this.sun); } } switchTheme() { this.checked = !this.checked; this.changeTheme.emit(this.checked); } }
内容的提问来源于stack exchange,提问作者Fadly Dzil
相关产品推荐
相关产品推荐

