65 lines
2.0 KiB
JavaScript
65 lines
2.0 KiB
JavaScript
class ThemeManager {
|
|
constructor() {
|
|
// Cache DOM elements once
|
|
this.toggle = document.getElementById('theme-toggle');
|
|
this.icon = document.getElementById('theme-icon');
|
|
|
|
// Get data attributes once
|
|
const { iconBase, iconDark, iconLight, soundSrc } = this.toggle.dataset;
|
|
this.iconBase = iconBase;
|
|
this.iconDark = iconDark;
|
|
this.iconLight = iconLight;
|
|
|
|
// Create audio element only when needed
|
|
this.sound = new Audio(soundSrc);
|
|
|
|
this.init();
|
|
}
|
|
|
|
init() {
|
|
this.setInitialTheme();
|
|
this.toggle.addEventListener('click', () => this.toggleTheme());
|
|
}
|
|
|
|
setInitialTheme() {
|
|
const savedTheme = localStorage.getItem('theme');
|
|
const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
const initialTheme = savedTheme || (systemDark ? 'dark' : 'light');
|
|
|
|
document.documentElement.setAttribute('data-theme', initialTheme);
|
|
this.updateIcon(initialTheme === 'dark');
|
|
}
|
|
|
|
toggleTheme() {
|
|
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
|
|
const newTheme = isDark ? 'light' : 'dark';
|
|
|
|
document.body.classList.add('theme-transition');
|
|
document.documentElement.setAttribute('data-theme', newTheme);
|
|
|
|
// Use the inverse to update the icon to match the new theme
|
|
this.updateIcon(!isDark);
|
|
|
|
localStorage.setItem('theme', newTheme);
|
|
|
|
// Use requestAnimationFrame for better performance on transition
|
|
requestAnimationFrame(() => {
|
|
setTimeout(() => {
|
|
document.body.classList.remove('theme-transition');
|
|
}, 300);
|
|
});
|
|
|
|
this.sound.play().catch(() => {});
|
|
}
|
|
|
|
|
|
// Extracted common functionality
|
|
updateIcon(isDark) {
|
|
this.icon.setAttribute('href',
|
|
`${this.iconBase}${isDark ? this.iconDark : this.iconLight}`);
|
|
}
|
|
}
|
|
|
|
// Use modern syntax
|
|
document.addEventListener('DOMContentLoaded', () => new ThemeManager());
|