mirror of
https://github.com/afoim/fuwari.git
synced 2026-01-31 09:03:18 +08:00
feat(主题设置): 添加彩虹模式功能并允许切换主题模式
- 在主题设置中添加彩虹模式,支持动态变换主题颜色 - 新增主题模式切换功能(亮色/暗色/自动) - 修改颜色选择器逻辑,支持临时保存颜色值 - 添加彩虹模式速率控制功能 - 移除强制暗色模式和固定主题颜色的限制
This commit is contained in:
@@ -1,21 +1,108 @@
|
||||
<script lang="ts">
|
||||
|
||||
import { onMount } from "svelte";
|
||||
import Icon from "@iconify/svelte";
|
||||
import { getDefaultHue, getHue, setHue } from "@utils/setting-utils";
|
||||
import {
|
||||
getDefaultHue,
|
||||
getHue,
|
||||
setHue,
|
||||
getStoredTheme,
|
||||
setTheme,
|
||||
getRainbowMode,
|
||||
setRainbowMode,
|
||||
getRainbowSpeed,
|
||||
setRainbowSpeed,
|
||||
} from "@utils/setting-utils";
|
||||
import { AUTO_MODE, DARK_MODE, LIGHT_MODE } from "@constants/constants";
|
||||
|
||||
let hue = getHue();
|
||||
let theme = getStoredTheme();
|
||||
let isRainbowMode = getRainbowMode();
|
||||
let rainbowSpeed = getRainbowSpeed();
|
||||
let animationId: number;
|
||||
|
||||
const defaultHue = getDefaultHue();
|
||||
|
||||
function resetHue() {
|
||||
hue = getDefaultHue();
|
||||
}
|
||||
|
||||
$: if (hue || hue === 0) {
|
||||
$: if ((hue || hue === 0) && !isRainbowMode) {
|
||||
setHue(hue);
|
||||
}
|
||||
|
||||
function switchTheme(newTheme: string) {
|
||||
theme = newTheme;
|
||||
setTheme(newTheme);
|
||||
}
|
||||
|
||||
function updateRainbow() {
|
||||
if (!isRainbowMode) return;
|
||||
|
||||
hue = (hue + rainbowSpeed * 0.05) % 360;
|
||||
setHue(hue, false);
|
||||
|
||||
animationId = requestAnimationFrame(updateRainbow);
|
||||
}
|
||||
|
||||
function toggleRainbow() {
|
||||
isRainbowMode = !isRainbowMode;
|
||||
setRainbowMode(isRainbowMode);
|
||||
|
||||
if (isRainbowMode) {
|
||||
updateRainbow();
|
||||
} else {
|
||||
cancelAnimationFrame(animationId);
|
||||
setHue(hue, true);
|
||||
}
|
||||
}
|
||||
|
||||
function onSpeedChange() {
|
||||
setRainbowSpeed(rainbowSpeed);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (isRainbowMode) {
|
||||
updateRainbow();
|
||||
}
|
||||
return () => {
|
||||
if (animationId) cancelAnimationFrame(animationId);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="display-setting" class="float-panel float-panel-closed absolute transition-all w-80 right-4 px-4 py-4">
|
||||
<div class="flex flex-row gap-2 mb-3 items-center justify-between">
|
||||
<div class="flex gap-2 font-bold text-lg text-neutral-900 dark:text-neutral-100 transition relative ml-3
|
||||
before:w-1 before:h-4 before:rounded-md before:bg-[var(--primary)]
|
||||
before:absolute before:-left-3 before:top-[0.33rem]"
|
||||
>
|
||||
主题模式
|
||||
</div>
|
||||
<div class="flex gap-1">
|
||||
<button aria-label="Light Mode"
|
||||
class="w-10 h-7 rounded-md transition flex items-center justify-center active:scale-90
|
||||
{theme === LIGHT_MODE ? 'bg-[var(--primary)] text-white' : 'bg-[var(--btn-regular-bg)] text-[var(--btn-content)] hover:bg-[var(--btn-regular-bg-hover)]'}"
|
||||
on:click={() => switchTheme(LIGHT_MODE)}
|
||||
>
|
||||
<Icon icon="material-symbols:wb-sunny-rounded" class="text-[1.1rem]"></Icon>
|
||||
</button>
|
||||
<button aria-label="Dark Mode"
|
||||
class="w-10 h-7 rounded-md transition flex items-center justify-center active:scale-90
|
||||
{theme === DARK_MODE ? 'bg-[var(--primary)] text-white' : 'bg-[var(--btn-regular-bg)] text-[var(--btn-content)] hover:bg-[var(--btn-regular-bg-hover)]'}"
|
||||
on:click={() => switchTheme(DARK_MODE)}
|
||||
>
|
||||
<Icon icon="material-symbols:dark-mode-rounded" class="text-[1.1rem]"></Icon>
|
||||
</button>
|
||||
<button aria-label="Auto Mode"
|
||||
class="w-10 h-7 rounded-md transition flex items-center justify-center active:scale-90
|
||||
{theme === AUTO_MODE ? 'bg-[var(--primary)] text-white' : 'bg-[var(--btn-regular-bg)] text-[var(--btn-content)] hover:bg-[var(--btn-regular-bg-hover)]'}"
|
||||
on:click={() => switchTheme(AUTO_MODE)}
|
||||
>
|
||||
<Icon icon="material-symbols:hdr-auto-rounded" class="text-[1.1rem]"></Icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-2 mb-3 items-center justify-between">
|
||||
<div class="flex gap-2 font-bold text-lg text-neutral-900 dark:text-neutral-100 transition relative ml-3
|
||||
before:w-1 before:h-4 before:rounded-md before:bg-[var(--primary)]
|
||||
@@ -32,14 +119,45 @@ $: if (hue || hue === 0) {
|
||||
<div class="flex gap-1">
|
||||
<div id="hueValue" class="transition bg-[var(--btn-regular-bg)] w-10 h-7 rounded-md flex justify-center
|
||||
font-bold text-sm items-center text-[var(--btn-content)]">
|
||||
{hue}
|
||||
{Math.round(hue)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full h-6 px-1 bg-[oklch(0.80_0.10_0)] dark:bg-[oklch(0.70_0.10_0)] rounded select-none">
|
||||
<input aria-label="主题色彩" type="range" min="0" max="360" bind:value={hue}
|
||||
<div class="w-full h-6 px-1 bg-[oklch(0.80_0.10_0)] dark:bg-[oklch(0.70_0.10_0)] rounded select-none mb-3">
|
||||
<input aria-label="主题色彩" type="range" min="0" max="360" bind:value={hue} disabled={isRainbowMode}
|
||||
class="slider" id="colorSlider" step="5" style="width: 100%">
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row gap-2 mb-3 items-center justify-between">
|
||||
<div class="flex gap-2 font-bold text-lg text-neutral-900 dark:text-neutral-100 transition relative ml-3
|
||||
before:w-1 before:h-4 before:rounded-md before:bg-[var(--primary)]
|
||||
before:absolute before:-left-3 before:top-[0.33rem]"
|
||||
>
|
||||
彩虹模式
|
||||
</div>
|
||||
<input type="checkbox" class="toggle-switch" checked={isRainbowMode} on:change={toggleRainbow} />
|
||||
</div>
|
||||
|
||||
{#if isRainbowMode}
|
||||
<div class="flex flex-row gap-2 mb-3 items-center justify-between transition-all" >
|
||||
<div class="flex gap-2 font-bold text-lg text-neutral-900 dark:text-neutral-100 transition relative ml-3
|
||||
before:w-1 before:h-4 before:rounded-md before:bg-[var(--primary)]
|
||||
before:absolute before:-left-3 before:top-[0.33rem]"
|
||||
>
|
||||
变换速率
|
||||
</div>
|
||||
<div class="flex gap-1">
|
||||
<div class="transition bg-[var(--btn-regular-bg)] w-10 h-7 rounded-md flex justify-center
|
||||
font-bold text-sm items-center text-[var(--btn-content)]">
|
||||
{rainbowSpeed}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="w-full h-6 px-1 bg-[var(--btn-regular-bg)] rounded select-none">
|
||||
<input aria-label="变换速率" type="range" min="1" max="100" bind:value={rainbowSpeed} on:change={onSpeedChange}
|
||||
class="slider" step="1" style="width: 100%">
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -89,4 +207,28 @@ $: if (hue || hue === 0) {
|
||||
&:active
|
||||
background rgba(255, 255, 255, 0.6)
|
||||
|
||||
.toggle-switch
|
||||
appearance none
|
||||
width 3rem
|
||||
height 1.5rem
|
||||
background var(--btn-regular-bg)
|
||||
border-radius 999px
|
||||
position relative
|
||||
cursor pointer
|
||||
transition background 0.3s
|
||||
&::after
|
||||
content ''
|
||||
position absolute
|
||||
top 0.25rem
|
||||
left 0.25rem
|
||||
width 1rem
|
||||
height 1rem
|
||||
background var(--btn-content)
|
||||
border-radius 50%
|
||||
transition transform 0.3s
|
||||
&:checked
|
||||
background var(--primary)
|
||||
&::after
|
||||
transform translateX(1.5rem)
|
||||
background white
|
||||
</style>
|
||||
|
||||
@@ -20,8 +20,8 @@ export const siteConfig: SiteConfig = {
|
||||
lang: "zh_CN", // 'en', 'zh_CN', 'zh_TW', 'ja', 'ko', 'es', 'th'
|
||||
themeColor: {
|
||||
hue: 361, // Default hue for the theme color, from 0 to 360. e.g. red: 0, teal: 200, cyan: 250, pink: 345
|
||||
fixed: true, // Hide the theme color picker for visitors
|
||||
forceDarkMode: true, // Force dark mode and hide theme switcher
|
||||
fixed: false, // Hide the theme color picker for visitors
|
||||
forceDarkMode: false, // Force dark mode and hide theme switcher
|
||||
},
|
||||
banner: {
|
||||
enable: false,
|
||||
|
||||
@@ -18,8 +18,10 @@ export function getHue(): number {
|
||||
return stored ? Number.parseInt(stored) : getDefaultHue();
|
||||
}
|
||||
|
||||
export function setHue(hue: number): void {
|
||||
localStorage.setItem("hue", String(hue));
|
||||
export function setHue(hue: number, save: boolean = true): void {
|
||||
if (save) {
|
||||
localStorage.setItem("hue", String(hue));
|
||||
}
|
||||
const r = document.querySelector(":root") as HTMLElement;
|
||||
if (!r) {
|
||||
return;
|
||||
@@ -27,6 +29,24 @@ export function setHue(hue: number): void {
|
||||
r.style.setProperty("--hue", String(hue));
|
||||
}
|
||||
|
||||
export function getRainbowMode(): boolean {
|
||||
const stored = localStorage.getItem("rainbow-mode");
|
||||
return stored === "true";
|
||||
}
|
||||
|
||||
export function setRainbowMode(enabled: boolean): void {
|
||||
localStorage.setItem("rainbow-mode", String(enabled));
|
||||
}
|
||||
|
||||
export function getRainbowSpeed(): number {
|
||||
const stored = localStorage.getItem("rainbow-speed");
|
||||
return stored ? Number.parseFloat(stored) : 5; // Default speed
|
||||
}
|
||||
|
||||
export function setRainbowSpeed(speed: number): void {
|
||||
localStorage.setItem("rainbow-speed", String(speed));
|
||||
}
|
||||
|
||||
export function applyThemeToDocument(theme: LIGHT_DARK_MODE) {
|
||||
switch (theme) {
|
||||
case LIGHT_MODE:
|
||||
|
||||
Reference in New Issue
Block a user