网红主题控件
1536 字
8 分钟
网红主题控件
这篇文章用于展示一个网红的主题切换控件。
在手机端和桌面端都可以使用上方控件切换明暗模式。
参考来源:
- https://github.com/SunnyDesignor/WpfGorgeousThemeSwitch
- https://codepen.io/linxiang-webcraft/pen/pvbvLBN
- 辅助:Gemini 3 Pro
源码:
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Theme Switch Component</title> <style> body { display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; background-color: #f0f0f0; transition: background-color 0.3s; }
body.dark { background-color: #1a1b26; }
/* Component Styles */ .theme-switch-wrapper { display: flex; align-items: center; justify-content: center; margin-top: 0.25rem; }
.theme-toggle { --base-scale: 15px; /* Adjusted for smaller size */ --toggle-width: 5.625em; --toggle-height: 2.5em; --radius-pill: 100em; --sun-diameter: 2.125em; --sun-offset: calc((var(--toggle-height) - var(--sun-diameter)) / 2);
--bg-toggle-light: #3d7eae; --bg-toggle-dark: #1d1f2c; --color-sun: #ecca2f; --color-sun-totem: #d6b025; --color-moon: #c4c9d1; --color-crater: #959db1; --color-star: #fff; --color-cloud-front: #f3fdff; --color-cloud-back: #aacadf;
--shadow-primary: rgba(0, 0, 0, 0.25); --shadow-highlight: rgba(255, 255, 255, 0.94); --halo-color: rgba(255, 255, 255, 0.1);
font-size: var(--base-scale); background: none; border: none; padding: 0; cursor: pointer; outline: none; -webkit-tap-highlight-color: transparent; }
.theme-toggle.collapsed { --toggle-width: 2.5em; /* Collapse to circle */ }
.theme-toggle__container { width: var(--toggle-width); height: var(--toggle-height); background-color: var(--bg-toggle-light); border-radius: var(--radius-pill);
position: relative; transition: background-color 0.3s ease, width 0.5s cubic-bezier(0.25, 1, 0.5, 1); overflow: hidden; box-shadow: 0.06em 0.06em 0.125em var(--shadow-highlight); }
.theme-toggle__container.dark { background-color: var(--bg-toggle-dark); box-shadow: none; }
.theme-toggle__container::after { content: ""; position: absolute; inset: 0; box-shadow: 0em 0.05em 0.187em rgba(0, 0, 0, 0.5) inset; border-radius: var(--radius-pill); pointer-events: none; }
.theme-toggle__sun-wrapper { position: absolute; top: var(--sun-offset); left: var(--sun-offset); width: var(--sun-diameter); height: var(--sun-diameter); z-index: 10; /* transform handled by inline style */ transition: transform 0.5s cubic-bezier(0.25, 1, 0.5, 1); }
.theme-toggle__container.bounce-enabled .theme-toggle__sun-wrapper { transition: transform 0.6s cubic-bezier(0.25, 1.25, 0.4, 1); }
.theme-toggle__sun { width: 100%; height: 100%; background-color: var(--color-sun); border-radius: 50%; overflow: hidden; box-shadow: 0.05em 0.125em 0.125em var(--shadow-primary), 0em 0.05em 0.125em var(--shadow-primary), 0.05em 0.05em 0.05em 0em rgba(254, 255, 239, 0.61) inset, 0em -0.05em 0.05em 0em #a1872a inset, 0 0 0 0.625em var(--halo-color), 0 0 0 1.25em var(--halo-color), 0 0 0 1.875em var(--halo-color); }
.theme-toggle__totem { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; }
.theme-toggle__totem path { stroke: var(--color-sun-totem); stroke-width: 5; stroke-linecap: round; fill: none; filter: drop-shadow(0 1px 0 rgba(255, 255, 255, 0.5)); }
.theme-toggle__moon-mask { position: relative; width: 100%; height: 100%; background-color: var(--color-moon); border-radius: inherit; transform: translateX(100%); transition: transform 0.3s ease;
box-shadow: 0.05em 0.05em 0.05em 0em rgba(255, 255, 255, 0.9) inset, 0em -0.05em 0.05em 0em #959db1 inset; }
.theme-toggle__container.bounce-enabled .theme-toggle__moon-mask { transition: transform 0.6s cubic-bezier(0.25, 1.25, 0.4, 1); }
.theme-toggle__container.dark .theme-toggle__moon-mask { transform: translateX(0); }
.theme-toggle__crater { position: absolute; background-color: var(--color-crater); border-radius: 50%; box-shadow: 0em 0.03em 0.06em var(--shadow-primary) inset; }
.theme-toggle__crater:nth-of-type(1) { top: 0.75em; left: 0.3em; width: 0.75em; height: 0.75em; }
.theme-toggle__crater:nth-of-type(2) { top: 0.9em; left: 1.375em; width: 0.375em; height: 0.375em; }
.theme-toggle__crater:nth-of-type(3) { top: 0.3em; left: 0.8em; width: 0.25em; height: 0.25em; }
/* Stars */ .theme-toggle__stars { position: absolute; left: 0; top: 0; width: 100%; height: 100%; transform: translateY(-100%); transition: transform 0.3s ease; pointer-events: none; }
.theme-toggle__container.bounce-enabled .theme-toggle__stars { transition: transform 0.6s cubic-bezier(0.25, 1.25, 0.4, 1); }
.theme-toggle__container.dark .theme-toggle__stars { transform: translateY(0); }
.star { position: absolute; width: 0.29em; height: 0.29em; }
/* WPF Star Positions (converted to em, 1em = 17px) */ .star-1 { left: 1.21em; top: 0.32em; width: 0.41em; height: 0.41em; animation: twinkle 2.5s infinite 1.3s; } /* star-2 removed */ .star-3 { left: 1.45em; top: 1.56em; width: 0.29em; height: 0.29em; animation: twinkle 3.1s infinite 2.0s; } .star-4 { left: 0.59em; top: 0.65em; width: 0.18em; height: 0.18em; animation: twinkle 3.3s infinite 2.2s; } /* star-5 removed */
/* Former dots converted to stars */ .star-6 { left: 0.68em; top: 1.56em; width: 0.29em; height: 0.29em; animation: twinkle 2.4s infinite 1.4s; } .star-7 { left: 1.9em; top: 0.74em; width: 0.35em; height: 0.35em; animation: twinkle 2.7s infinite 1.8s; } .star-8 { left: 1.39em; top: 1.06em; width: 0.24em; height: 0.24em; animation: twinkle 3.0s infinite 2.3s; } .star-9 { left: 2em; top: 1.41em; width: 0.18em; height: 0.18em; animation: twinkle 2.9s infinite 1.5s; } /* star-10 removed */ .star-11 { left: 2.32em; top: 0.47em; width: 0.29em; height: 0.29em; animation: twinkle 3.5s infinite 2.1s; }
@keyframes twinkle { 0%, 100% { transform: scale(1); opacity: 1; } 50% { transform: scale(0); opacity: 0.5; } }
/* Clouds */ .theme-toggle__clouds { position: absolute; left: 0; top: 0; width: 100%; height: 100%; pointer-events: none; }
.cloud { position: absolute; width: 5.88em; height: 2.35em; transition: transform 0.6s ease-in-out; }
.theme-toggle__container.bounce-enabled .cloud { transition: transform 0.6s cubic-bezier(0.25, 1.25, 0.4, 1); }
.theme-toggle__container.dark .cloud { transition: none; }
.cloud-1 { top: 0.35em; left: 0; animation: Cloud1Bounce 3.6s cubic-bezier(0.25, 1, 0.5, 1) forwards; }
.theme-toggle__container.dark .cloud-1 { transform: translateY(2.35em); animation: none; }
@keyframes Cloud1Bounce { 0% { transform: translateY(2em); } 16% { transform: translateY(-0.18em); } 66% { transform: translateY(0.12em); } 100% { transform: translateY(0); } }
.cloud-2 { top: 0.71em; left: 0; animation: Cloud2Bounce 2.8s cubic-bezier(0.25, 1, 0.5, 1) forwards; }
.theme-toggle__container.dark .cloud-2 { transform: translateY(1.76em); animation: none; }
@keyframes Cloud2Bounce { 0% { transform: translateY(1.53em); } 21% { transform: translateY(-0.12em); } 64% { transform: translateY(0.09em); } 100% { transform: translateY(0); } } </style></head><body>
<div class="theme-switch-wrapper"> <button id="themeToggle" class="theme-toggle collapsed" aria-label="Toggle Theme"> <div class="theme-toggle__container"> <div class="theme-toggle__stars"> <svg class="star star-1" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <path d="M100,50 C72.38589,50 50,72.38589 50,100 C50,72.38589 27.61411,50 0,50 C27.61411,50 50,27.61411 50,0 C50,27.61411 72.38589,50 100,50 z" fill="white"/> </svg> <svg class="star star-3" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <path d="M100,50 C72.38589,50 50,72.38589 50,100 C50,72.38589 27.61411,50 0,50 C27.61411,50 50,27.61411 50,0 C50,27.61411 72.38589,50 100,50 z" fill="white"/> </svg> <svg class="star star-4" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <path d="M100,50 C72.38589,50 50,72.38589 50,100 C50,72.38589 27.61411,50 0,50 C27.61411,50 50,27.61411 50,0 C50,27.61411 72.38589,50 100,50 z" fill="white"/> </svg> <svg class="star star-6" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <path d="M100,50 C72.38589,50 50,72.38589 50,100 C50,72.38589 27.61411,50 0,50 C27.61411,50 50,27.61411 50,0 C50,27.61411 72.38589,50 100,50 z" fill="white"/> </svg> <svg class="star star-7" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <path d="M100,50 C72.38589,50 50,72.38589 50,100 C50,72.38589 27.61411,50 0,50 C27.61411,50 50,27.61411 50,0 C50,27.61411 72.38589,50 100,50 z" fill="white"/> </svg> <svg class="star star-8" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <path d="M100,50 C72.38589,50 50,72.38589 50,100 C50,72.38589 27.61411,50 0,50 C27.61411,50 50,27.61411 50,0 C50,27.61411 72.38589,50 100,50 z" fill="white"/> </svg> <svg class="star star-9" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <path d="M100,50 C72.38589,50 50,72.38589 50,100 C50,72.38589 27.61411,50 0,50 C27.61411,50 50,27.61411 50,0 C50,27.61411 72.38589,50 100,50 z" fill="white"/> </svg> <svg class="star star-11" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <path d="M100,50 C72.38589,50 50,72.38589 50,100 C50,72.38589 27.61411,50 0,50 C27.61411,50 50,27.61411 50,0 C50,27.61411 72.38589,50 100,50 z" fill="white"/> </svg> </div>
<div class="theme-toggle__clouds"> <svg class="cloud cloud-1" viewBox="155 290 372 178" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg"> <path d="M158.99223,453.55724 C169.78284,423.31232 203.15378,392.41965 241.59875,404.12313 261.99875,383.72313 299.3,391.57463 305.3,392.77701 330.15525,362.39707 353.16314,370.00505 368.58869,378.91567 370.80287,377.8725 376.53823,377.01298 381.06677,376.82559 387.43795,354.90123 410.63981,354.27147 420.4351,355.75324 432.85079,341.48144 440.27525,342.83843 451.87985,342.32945 457.75133,307.27233 472.82215,288.94564 509.06064,292.16184 512.31051,292.45027 525.93938,292.61638 525.93938,292.61638 525.90428,295.34879 526.73875,460.86787 526.8053,465.68179 L154.99571,465.6552 z" fill="#ffffff" fill-opacity="0.53"/> </svg> <svg class="cloud cloud-2" viewBox="155 290 372 178" preserveAspectRatio="none" xmlns="http://www.w3.org/2000/svg"> <path d="M158.99223,453.55724 C169.78284,423.31232 203.15378,392.41965 241.59875,404.12313 261.99875,383.72313 299.3,391.57463 305.3,392.77701 337.22357,353.75764 390.50003,373.43798 398.90003,384.97667 412.50003,378.21261 430.04088,381.41688 430.04088,381.41688 436.82018,362.13027 456.18064,352.96024 465.00342,351.59457 460.57167,304.02021 510.55723,292.35177 521.33687,292.25901 530.92623,292.1765 525.93938,292.61638 525.93938,292.61638 525.90428,295.34879 526.73875,460.86787 526.8053,465.68179 L154.99571,465.6552 z" fill="white"/> </svg> </div>
<div class="theme-toggle__sun-wrapper"> <div class="theme-toggle__sun"> <svg class="theme-toggle__totem" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"> <g class="totem-rays"> <path d="M50,35 Q55,28 50,21 T50,12" transform="rotate(0 50 50)" /> <path d="M50,35 Q55,28 50,21 T50,12" transform="rotate(45 50 50)" /> <path d="M50,35 Q55,28 50,21 T50,12" transform="rotate(90 50 50)" /> <path d="M50,35 Q55,28 50,21 T50,12" transform="rotate(135 50 50)" /> <path d="M50,35 Q55,28 50,21 T50,12" transform="rotate(180 50 50)" /> <path d="M50,35 Q55,28 50,21 T50,12" transform="rotate(225 50 50)" /> <path d="M50,35 Q55,28 50,21 T50,12" transform="rotate(270 50 50)" /> <path d="M50,35 Q55,28 50,21 T50,12" transform="rotate(315 50 50)" /> </g> </svg> <div class="theme-toggle__moon-mask"> <div class="theme-toggle__crater"></div> <div class="theme-toggle__crater"></div> <div class="theme-toggle__crater"></div> </div> </div> </div> </div> </button></div>
<script> // Constants const LIGHT_MODE = "light"; const DARK_MODE = "dark"; const AUTO_MODE = "auto"; const DEFAULT_THEME = AUTO_MODE;
// State let mode = DEFAULT_THEME; let systemIsDark = false; let isHovered = false; let shouldBounce = false;
// DOM Elements const toggleButton = document.getElementById('themeToggle'); const container = toggleButton.querySelector('.theme-toggle__container'); const sunWrapper = toggleButton.querySelector('.theme-toggle__sun-wrapper');
// Initialization function init() { const storedTheme = localStorage.getItem("theme"); mode = storedTheme || DEFAULT_THEME;
const darkModePreference = window.matchMedia("(prefers-color-scheme: dark)"); systemIsDark = darkModePreference.matches;
// Listen for system preference changes darkModePreference.addEventListener("change", (e) => { systemIsDark = e.matches; update(); });
// Event Listeners toggleButton.addEventListener('click', toggleScheme); toggleButton.addEventListener('mouseenter', () => { isHovered = true; shouldBounce = false; update(); }); toggleButton.addEventListener('mouseleave', () => { isHovered = false; shouldBounce = false; update(); });
update(); }
function isDark() { return mode === DARK_MODE || (mode === AUTO_MODE && systemIsDark); }
function toggleScheme() { shouldBounce = true; if (mode === DARK_MODE) { mode = LIGHT_MODE; } else if (mode === LIGHT_MODE) { mode = DARK_MODE; } else { // Auto mode logic if (systemIsDark) { mode = LIGHT_MODE; } else { mode = DARK_MODE; } }
localStorage.setItem("theme", mode); update(); }
function update() { const dark = isDark();
// Update document theme (for body background demo) if (dark) { document.body.classList.add('dark'); } else { document.body.classList.remove('dark'); }
// Update Container Classes if (dark) { container.classList.add('dark'); } else { container.classList.remove('dark'); }
if (shouldBounce) { container.classList.add('bounce-enabled'); } else { container.classList.remove('bounce-enabled'); }
// Update Toggle Button Classes if (!isHovered) { toggleButton.classList.add('collapsed'); } else { toggleButton.classList.remove('collapsed'); }
// Update Sun Transform let transformValue; if (!isHovered) { transformValue = "translateX(0)"; } else if (dark) { transformValue = "translateX(2.938em)"; } else { transformValue = "translateX(0.187em)"; } sunWrapper.style.transform = transformValue; }
// Run init init();</script>
</body></html> 站点统计
6
4
14
7,003
0 天
0 天前
日
一
二
三
四
五
六