Repository Wiki
LyraVoid/Mizuki

视觉特效与全屏壁纸

Mizuki 前端提供"全屏壁纸"视觉模式:在内容层之下渲染一张固定定位、可轮播、可调节透明度/模糊度的背景图,并允许用户在 Banner 模式、全屏壁纸模式与无壁纸模式之间切换。本文覆盖该能力的配置层、渲染层与交互层的完整实现。

Purpose and Scope

本页完整讲解全屏壁纸与相关视觉特效的实现,范围包括:

  • 配置层:fullscreenWallpaperConfig(src/config/backgroundWallpaper.ts)如何声明图片源、轮播、透明度、模糊与可切换项;
  • 渲染层:FullscreenWallpaper.astro(src/components/misc/FullscreenWallpaper.astro)如何在 Astro 构建期解析图片源、在客户端用零依赖的内联脚本驱动轮播与 CSS 变量;
  • 交互层:WallpaperSwitch.svelte(src/components/features/settings/WallpaperSwitch.svelte)提供的三档壁纸模式切换面板;
  • 持久化契约:localStorage / sessionStorage 中与壁纸相关的键,以及 --wallpaper-opacity、--wallpaper-blur、--card-transparent-opacity 三个 CSS 自定义属性。

以下内容有意留给兄弟页面,不在本页展开:站点级 siteConfig 与 Banner 图 API(imageApi)的通用定义、Image 原子组件的响应式图片管线、设置面板框架 panelManager 的通用实现,以及卡片透明度等主题变量在其他组件中的消费方式。src/styles/wallpaper-navbar-transparent.css 属于壁纸模式下的导航栏透明样式,本页仅在性能与联动部分提及。

Overview

全屏壁纸是 Mizuki 主题的视觉基调能力。它解决三个问题:

  1. 氛围渲染:在桌面端与移动端分别提供壁纸图集(/assets/desktop-banner/、/assets/mobile-banner/),支持多图轮播或随机单图;
  2. 可读性控制:壁纸不能压过正文,因此壁纸容器默认 opacity: 0.8、blur: 1px、z-index: -1,并且 pointer-events-none,永远不会拦截用户交互;
  3. 用户偏好:三种模式(banner / fullscreen / none)与 overlay 模式下的透明度、模糊、卡片不透明度均可由用户在设置面板中调节,并写入 localStorage,刷新后由一段构建期内联的脚本在首帧前恢复,避免闪烁。

关键术语:

  • wallpaperMode:用户选择的壁纸模式,取值为 WALLPAPER_BANNER / WALLPAPER_FULLSCREEN / WALLPAPER_NONE(常量定义于 @constants/constants),持久化在 localStorage.wallpaperMode;
  • overlay 模式:wallpaperMode === 'overlay' 时壁纸全屏显示,且可叠加用户自定义的透明度/模糊/卡片不透明度;非 overlay 模式下壁纸容器直接 display: none;
  • 轮播(carousel):多图时按 carousel.interval 秒轮换淡入淡出;索引持久化在 sessionStorage,同一会话内不重置。

Architecture

Loading diagram...

分层意图说明:

  • 配置 → 渲染是单向数据流:FullscreenWallpaper.astro 接收一个 FullscreenWallpaperConfig 类型的 prop,而不是自己去读全局配置对象。这让壁纸组件可以独立复用与测试。
  • 交互层与渲染层通过存储解耦:WallpaperSwitch.svelte 只写 localStorage(经 setting-utils),FullscreenWallpaper.astro 的内联脚本在页面加载时读取同一批键。二者没有直接调用关系,切换模式后的即时生效依赖设置面板一侧的逻辑,本页仅记录其存储契约。
  • 轮播运行时是零依赖的:FullscreenWallpaper.astro 中所有客户端脚本都使用 Astro 的 is:inline,不会被打包为模块,也不依赖任何前端框架运行时;这是为了让壁纸在 SSG 静态页面上开箱即用。

渲染层实现:FullscreenWallpaper.astro

图片源解析:API 优先 + 设备回退

组件 props 只有两个:config: FullscreenWallpaperConfig 与可选 className。图片源解析函数 getImageSources() 实现了一条明确的优先级链:

astro
1 // 获取当前设备类型的图片源 2 const getImageSources = async () => { 3 const srcConfig = config.src; 4 5 // API 图片逻辑 6 if (siteConfig.banner.imageApi?.enable && siteConfig.banner.imageApi?.url) { 7 try { 8 const response = await fetch(siteConfig.banner.imageApi.url); 9 const text = await response.text(); 10 const apiImages = text.split("\n").filter((line) => line.trim()); 11 if (apiImages.length > 0) { 12 return { desktop: apiImages, mobile: apiImages }; 13 } 14 } catch (error) { 15 console.warn("Failed to fetch images from API:", error); 16 } 17 } 18 19 const toArray = (src: string | string[] | undefined): string[] => 20 Array.isArray(src) ? src : typeof src === "string" ? [src] : []; 21 22 if ( 23 typeof srcConfig === "object" && 24 srcConfig !== null && 25 !Array.isArray(srcConfig) 26 ) { 27 const desktop = toArray(srcConfig.desktop); 28 const mobile = toArray(srcConfig.mobile); 29 return { 30 desktop: desktop.length > 0 ? desktop : mobile, 31 mobile: mobile.length > 0 ? mobile : desktop, 32 }; 33 } 34 35 // 如果是字符串或字符串数组,同时用于桌面端和移动端 36 const allImages = toArray(srcConfig as string | string[]); 37 return { desktop: allImages, mobile: allImages }; 38 }; 39 40 const imageSources = await getImageSources();

FullscreenWallpaper.astro

设计要点:

  1. API 覆盖是软失败:fetch 失败或返回空列表时只 console.warn,随后回退到本地配置。壁纸是装饰性资源,构建期宁可降级也不让页面构建失败。
  2. API 图源按换行符分隔:响应体按 \n 拆分并过滤空行,这是常见的"每行一个 URL"随机图 API 约定;命中后 desktop 与 mobile 共用同一列表。
  3. desktop / mobile 双向回退:对象型 src 下,desktop 为空用 mobile 补,反之亦然;字符串或字符串数组则两端共用。注意 config.src 的类型在类型层是宽松的,运行时由这段归一化代码兜底。
  4. 完全无图则不渲染:if (!hasDesktopImages && !hasMobileImages) return null;(FullscreenWallpaper.astro#L57-L59),保证配置为空时不会输出一个空的 fixed 容器。

响应式分组与轮播判定

解析出的两组图片被打包为 renderGroups,分别套上 hidden md:block(桌面,≥768px 显示)与 block md:hidden(移动)的容器类。轮播开启条件是 carousel.enable 且任一设备组图片数大于 1:

astro
1 // 轮播配置 2 const isCarouselEnabled = 3 config.carousel?.enable && 4 (imageSources.desktop.length > 1 || imageSources.mobile.length > 1); 5 const carouselInterval = config.carousel?.interval || 5;

FullscreenWallpaper.astro

轮播分支中每张图都通过 Image 原子组件输出,并针对设备做了差异化优化:

astro
1 <Image 2 src={src} 3 alt={`${group.key} wallpaper ${index + 1}`} 4 class="w-full h-full" 5 position={position} 6 widths={group.key === "mobile" ? [640, 1024, 1536] : [1024, 1536, 2048, 2560]} 7 sizes="100vw" 8 loading="lazy" 9 fetchpriority="low" 10 quality={group.key === "mobile" ? Math.round(wallpaperQuality * 0.9) : wallpaperQuality} 11 formats={wallpaperFormats} 12 overlay={false} 13 />

FullscreenWallpaper.astro

widths 与 sizes="100vw" 声明壁纸铺满视口;移动端宽度档位更低且质量乘以 0.9,是对移动网络与 DPR 的带宽让步。loading="lazy" + fetchpriority="low" 明确表达"壁纸不与首屏内容争抢资源"的意图。

容器样式与 CSS 变量契约

壁纸容器是一个 fixed 覆盖层,其视觉参数全部挂到 CSS 变量上,默认值来自配置:

astro
1<div 2 class:list={[ 3 "fixed inset-0 w-full h-full overflow-hidden pointer-events-none wallpaper-container", 4 className 5 ]} 6 style={`z-index: ${zIndex}; transform: translateZ(0); perspective: 1000px; opacity: var(--wallpaper-opacity, ${opacity}); filter: blur(var(--wallpaper-blur, ${blurAmount}px));`} 7 data-fullscreen-wallpaper 8>

FullscreenWallpaper.astro

三个关键决定:

  • pointer-events-none:壁纸永远不捕获鼠标/触摸事件,即便覆盖全屏也不会影响任何可交互元素;
  • opacity: var(--wallpaper-opacity, 0.8) 与 filter: blur(var(--wallpaper-blur, 1px)):变量未设置时使用配置默认值;一旦用户在 overlay 模式下自定义了数值,内联脚本写入的同名变量会覆盖默认值——这就是"配置提供基线、用户偏好提供覆盖"的机制;
  • transform: translateZ(0) / perspective: 1000px:把壁纸推到自己的合成层,避免长页面滚动时 fixed 背景被父级 transform 打掉或出现重绘抖动。

首帧恢复:防闪烁的内联脚本

模式与用户偏好的恢复脚本紧跟在容器开标签之后,is:inline 保证它出现在 HTML 流中并在图片渲染前同步执行:

astro
1<script is:inline define:vars={{ wallpaperEnabled: config.enable ?? true, overlaySwitchable: config.overlay?.switchable ?? true }}> 2(function() { 3 const root = document.documentElement; 4 const wallpaperEnabledVal = wallpaperEnabled; 5 const mode = wallpaperEnabledVal ? (localStorage.getItem('wallpaperMode') || 'banner') : 'banner'; 6 7 if (mode === 'overlay') { 8 const isSw = typeof overlaySwitchable === 'object' ? overlaySwitchable : { opacity: !!overlaySwitchable, blur: !!overlaySwitchable, cardOpacity: !!overlaySwitchable }; 9 const overlayOpacity = isSw.opacity ? localStorage.getItem('overlayOpacity') : null; 10 const overlayBlur = isSw.blur ? localStorage.getItem('overlayBlur') : null; 11 const overlayCardOpacity = isSw.cardOpacity ? localStorage.getItem('overlayCardOpacity') : null; 12 13 if (overlayOpacity) { 14 document.currentScript.parentElement.style.setProperty('--wallpaper-opacity', overlayOpacity); 15 } 16 if (overlayBlur) { 17 document.currentScript.parentElement.style.setProperty('--wallpaper-blur', overlayBlur + 'px'); 18 } 19 if (overlayCardOpacity) { 20 root.style.setProperty('--card-transparent-opacity', overlayCardOpacity); 21 } 22 } 23 24 if (mode !== 'overlay') { 25 root.style.removeProperty('--card-transparent-opacity'); 26 document.currentScript.parentElement.style.display = 'none'; 27 } 28})(); 29</script>

FullscreenWallpaper.astro

这段脚本揭示了两条隐含契约,扩展时必须遵守:

  1. overlaySwitchable 是一个"白名单":config.overlay.switchable 可以是布尔(全开/全关)或逐项对象 { opacity, blur, cardOpacity }。为 false 的项即使 localStorage 里有旧值也不会被读取——防止用户关闭"可调节透明度"后旧偏好仍然生效。
  2. 非 overlay 模式直接隐藏而非移除:display: none 会停掉合成与绘制,但不销毁 DOM,切回 overlay 时无需重新解析图片。同时它会移除 --card-transparent-opacity,让卡片透明度回到主题默认值。

注意脚本通过 document.currentScript.parentElement 定位到壁纸容器自身,而不是查询选择器——因为同一页面理论上可能有多个壁纸实例(例如布局与页眉各自渲染),每个实例只处理自己的样式。

交互层实现:WallpaperSwitch.svelte

设置面板中的壁纸切换按钮提供三档模式,通过 panelManager 管理浮层面板:

svelte
1const wallpaperOptions: { 2 mode: WALLPAPER_MODE; 3 icon: string; 4 label: I18nKey; 5}[] = [ 6 { 7 mode: WALLPAPER_BANNER, 8 icon: "material-symbols:image-outline", 9 label: I18nKey.wallpaperBanner, 10 }, 11 { 12 mode: WALLPAPER_FULLSCREEN, 13 icon: "material-symbols:wallpaper", 14 label: I18nKey.wallpaperFullscreen, 15 }, 16 { 17 mode: WALLPAPER_NONE, 18 icon: "material-symbols:hide-image-outline", 19 label: I18nKey.wallpaperNone, 20 }, 21]; 22 23let mode: WALLPAPER_MODE = $state(WALLPAPER_BANNER); 24 25onMount(() => { 26 mode = getStoredWallpaperMode(); 27}); 28 29function switchWallpaperMode(newMode: WALLPAPER_MODE) { 30 mode = newMode; 31 setWallpaperMode(newMode); 32} 33 34async function togglePanel() { 35 await panelManager.closeAllPanelsExcept("wallpaper-mode-panel"); 36 await panelManager.togglePanel("wallpaper-mode-panel"); 37}

WallpaperSwitch.svelte

实现要点:

  • 使用 Svelte 5 runes($state / $derived)管理本地选中态;onMount 里才调用 getStoredWallpaperMode() 同步持久化值,避免 SSR 与客户端首渲染不一致;
  • 面板按钮通过 data-active={mode === option.mode} 标记当前模式,并在 <style> 里用 background-color: var(--primary) !important 高亮(WallpaperSwitch.svelte#L93-L101);
  • 持久化与"立即生效"逻辑封装在 @utils/setting-utils 的 setWallpaperMode 中——该函数负责写 localStorage.wallpaperMode 并同步更新页面。其内部实现属于设置工具页的范畴,本页只依赖其签名契约 setWallpaperMode(mode: WALLPAPER_MODE) / getStoredWallpaperMode(): WALLPAPER_MODE。
  • togglePanel 先 closeAllPanelsExcept("wallpaper-mode-panel") 再 togglePanel,保证同一时刻只有一个浮层面板打开(避免壁纸面板与其他设置面板重叠)。

对应的渲染部分:

svelte
1{#each wallpaperOptions as option} 2 <button 3 class="flex transition whitespace-nowrap items-center justify-start! w-full btn-plain rounded-lg h-11 px-3 font-medium active:scale-95 theme-switch-btn mb-0.5 last:mb-0" 4 data-active={mode === option.mode} 5 class:scale-animation={mode !== option.mode} 6 role="menuitem" 7 onclick={() => switchWallpaperMode(option.mode)} 8 > 9 <Icon icon={option.icon} class="text-[1.25rem] mr-3"></Icon> 10 {i18n(option.label)} 11 </button> 12{/each}

WallpaperSwitch.svelte

Core Flow

下面两张图分别描述"页面加载时壁纸的初始化/恢复"与"多图轮播的运行时循环",均对应 FullscreenWallpaper.astro 中的真实代码路径。

初始化与偏好恢复(首帧)

Loading diagram...

同步执行是关键:恢复脚本位于容器开标签之后、图片元素之前,浏览器在解析到壁纸 <img> 之前就已确定 opacity/blur/display,因此用户看到的第一个画面就是最终状态。

轮播运行时循环

Loading diagram...

对应实现在 initFullscreenWallpaperCarousel()(FullscreenWallpaper.astro#L231-L334),核心循环如下:

js
1 function switchNextImage(stateItem) { 2 const { items, currentIndex } = stateItem; 3 const nextIndex = (currentIndex + 1) % items.length; 4 5 items[currentIndex].style.opacity = '0'; 6 items[nextIndex].style.opacity = '1'; 7 8 stateItem.currentIndex = nextIndex; 9 10 sessionStorage.setItem(STORAGE_KEY, nextIndex.toString()); 11 } 12 13 function start() { 14 state.forEach(s => { 15 if (s.active && !s.timer) { 16 s.timer = setInterval(() => { 17 if (!document.body.contains(s.items[0])) { 18 stop(); 19 return; 20 } 21 switchNextImage(s); 22 }, carouselInterval * 1000); 23 } 24 }); 25 }

FullscreenWallpaper.astro

每个轮播元素本身带有 transition-opacity duration-1000(1 秒淡入淡出,见 FullscreenWallpaper.astro#L142-L147),运行时只切换 style.opacity 的 0/1,由 CSS transition 完成平滑过渡;这是"JS 管状态、CSS 管动画"的典型分工。

非轮播模式:随机单图 + 防重复

carousel 未开启(或仅一张图)时走 #wallpaper-single-container 分支,由另一段内联脚本随机挑选图片:

js
1 const getRandomImage = (images, storageKey) => { 2 if (images.length === 0) {return null;} 3 if (images.length === 1) {return images[0];} 4 5 const lastIndex = sessionStorage.getItem(storageKey); 6 let newIndex; 7 8 do { 9 newIndex = Math.floor(Math.random() * images.length); 10 } while (newIndex === parseInt(lastIndex || '-1')); 11 12 sessionStorage.setItem(storageKey, newIndex.toString()); 13 return images[newIndex]; 14 }; 15 16 const desktopSrc = getRandomImage(desktopImages, 'wallpaper_desktop_index'); 17 const mobileSrc = getRandomImage(mobileImages, 'wallpaper_mobile_index');

FullscreenWallpaper.astro

do...while 保证同一会话内不会连续两次抽到同一张:随机到与上次相同就重抽。desktop 与 mobile 使用独立的存储键(wallpaper_desktop_index / wallpaper_mobile_index),两个断点互不影响。随后按断点动态构建 hidden lg:block / block lg:hidden 两个容器——注意这里用的是 lg:(1024px)而不是轮播分支的 md:(768px),是源码中的既有差异,扩展时需留意一致性。

配置参考:fullscreenWallpaperConfig

配置对象类型为 FullscreenWallpaperConfig(定义于 src/types/config),完整默认值如下:

ts
1export const fullscreenWallpaperConfig: FullscreenWallpaperConfig = { 2 enable: true, 3 src: { 4 desktop: [ 5 "/assets/desktop-banner/1.webp", 6 "/assets/desktop-banner/2.webp", 7 "/assets/desktop-banner/3.webp", 8 "/assets/desktop-banner/4.webp", 9 ], 10 mobile: [ 11 "/assets/mobile-banner/1.webp", 12 "/assets/mobile-banner/2.webp", 13 "/assets/mobile-banner/3.webp", 14 "/assets/mobile-banner/4.webp", 15 ], 16 }, 17 position: "center", 18 carousel: { 19 enable: true, 20 interval: 5, 21 }, 22 zIndex: -1, 23 opacity: 0.8, 24 blur: 1, 25 switchable: true, 26 overlay: { 27 opacity: 0.8, // 壁纸不透明度,0-1 28 blur: 1.5, // 背景模糊半径(px) 29 cardOpacity: 0.8, // 卡片不透明度,0-1 30 switchable: { 31 opacity: true, 32 blur: true, 33 cardOpacity: true, 34 }, 35 }, 36 fullscreen: { 37 switchable: { 38 opacity: true, 39 blur: true, 40 }, 41 }, 42};

backgroundWallpaper.ts

各字段在渲染层中的实际消费方式:

字段类型 / 默认值消费位置与行为
enableboolean / true内联恢复脚本的 wallpaperEnabled;为 false 时强制 mode = 'banner',全屏壁纸整体不可用
srcstring | string[] | { desktop, mobile }getImageSources() 归一化为 desktop/mobile 两组;对象形式支持各自回退
position"top" | "center" | "bottom" / "center"映射为 Tailwind 的 object-top / object-center / object-bottom,控制图片裁剪锚点(FullscreenWallpaper.astro#L74-L83)
carousel.enableboolean / true多图时启用轮播分支;否则走随机单图分支
carousel.intervalnumber(秒)/ 5setInterval 周期 = interval * 1000 ms
zIndexnumber / -1直接写入容器内联 z-index,保证壁纸位于内容之下
opacitynumber / 0.8作为 --wallpaper-opacity 的回退默认值(未被用户覆盖时生效)
blurnumber(px)/ 1作为 --wallpaper-blur 的回退默认值
overlay.switchableboolean | { opacity, blur, cardOpacity } / 全 true白名单:决定恢复脚本是否读取对应的 localStorage 键
fullscreen.switchable{ opacity, blur } / 全 true全屏(非 overlay)模式下的可调节项白名单

overlay 子对象的 opacity / blur / cardOpacity 三项数值本身在该组件内并未被直接用于计算默认 CSS 变量值——组件只读取外层 opacity / blur 作为默认值;overlay 数值由设置面板一侧消费(存储为 overlayOpacity / overlayBlur / overlayCardOpacity 后,再经恢复脚本写回 CSS 变量)。实现细节在源码中未完全可见:setting-utils 中对 overlay 数值的写入与实时预览逻辑未在本次读取范围内。

浏览器存储契约汇总

壁纸能力跨组件共享以下存储键,任何扩展都必须遵守同一契约:

存储键作用域写入方读取方语义
wallpaperModelocalStoragesetWallpaperMode(setting-utils)内联恢复脚本、getStoredWallpaperMode当前壁纸模式;缺省视为 'banner'
overlayOpacitylocalStorage设置面板(overlay 模式)内联恢复脚本(受 switchable.opacity 门控)写入 --wallpaper-opacity
overlayBlurlocalStorage设置面板(overlay 模式)内联恢复脚本(受 switchable.blur 门控)写入 --wallpaper-blur(追加 px)
overlayCardOpacitylocalStorage设置面板(overlay 模式)内联恢复脚本(受 switchable.cardOpacity 门控)写入根元素 --card-transparent-opacity
mizuki_wallpaper_indexsessionStorage轮播 switchNextImage轮播初始化 initImage当前轮播索引;会话内连续性
wallpaper_desktop_indexsessionStorage随机单图脚本同一脚本上次桌面随机索引,防连续重复
wallpaper_mobile_indexsessionStorage随机单图脚本同一脚本上次移动随机索引,防连续重复

选择 sessionStorage 存索引、localStorage 存偏好的原因是:轮播/随机的"连续性"只需要在同一浏览会话内成立(新会话重新随机更自然),而模式与透明度是跨会话的用户偏好。

API 参考

渲染层(FullscreenWallpaper.astro)

Props.config: FullscreenWallpaperConfig

壁纸的全部配置。必需。见上文配置表。

Props.className?: string

附加到壁纸容器上的额外类名,通过 class:list 与内置类合并。

getImageSources(): Promise<{ desktop: string[]; mobile: string[] }>

组件内部函数。返回归一化后的两组图片 URL。优先级:siteConfig.banner.imageApi(启用且成功且非空)> 对象型 src(含双向回退)> 字符串/数组型 src(两端共用)。API 失败仅 console.warn,不抛出。

initFullscreenWallpaperCarousel(): void

客户端全局函数(非导出模块,经 is:inline 注入)。查询 [data-fullscreen-wallpaper] 容器,为 desktop/mobile 两组 [data-carousel-item] 各建一个状态对象并启动 setInterval;注册 visibilitychange 监听;把清理函数挂到 window.__wallpaper_cleanup。容器不存在时直接返回。

交互层(WallpaperSwitch.svelte 内部)

switchWallpaperMode(newMode: WALLPAPER_MODE): void

更新本地 $state 并调用 setWallpaperMode(newMode) 完成持久化与页面即时切换。

togglePanel(): Promise<void>

先 panelManager.closeAllPanelsExcept("wallpaper-mode-panel"),再 panelManager.togglePanel("wallpaper-mode-panel") 展开/收起模式面板。

getStoredWallpaperMode(): WALLPAPER_MODE / setWallpaperMode(mode: WALLPAPER_MODE): void

来自 @utils/setting-utils 的持久化工具(签名由调用处确认;内部实现未在本次读取范围内)。

失败模式、边界与并发

  • 图片 API 不可用:构建期 fetch 抛错被捕获并 console.warn,自动回退本地 src;不阻塞构建。
  • 配置为空:desktop 与 mobile 均为空时组件返回 null,页面完全无壁纸 DOM。
  • enable: false:恢复脚本强制 mode = 'banner',即使 localStorage 中存有 'overlay' 也不会生效——配置优先级高于用户偏好,用于站点级禁用。
  • 轮播索引越界:initImage 之前先用 safeIndex = (savedIndex >= 0 && savedIndex < items.length) ? savedIndex : 0 钳制(FullscreenWallpaper.astro#L249-L257),图片数量变化或存储脏值都不会越界。
  • SPA/视图切换残留:轮播在每次 tick 前检查 document.body.contains(s.items[0]),节点被移除即 stop();同时 initFullscreenWallpaperCarousel 入口先执行旧 window.__wallpaper_cleanup,防止重复初始化造成多个并行计时器——这是该组件最重要的并发防护。
  • 后台标签页:visibilitychange 时暂停全部计时器,回到前台且节点仍在时重启,避免后台空转。
  • 单图随机死循环防护:仅当数组长度 > 1 才进入 do...while,长度为 1 直接返回首项,不可能死循环。
  • overlaySwitchable 兼容布尔与对象:脚本内做了归一化,布尔值会展开为三项同值,避免配置形态变化导致读取崩溃。

性能与扩展点

性能设计集中在三处:

  1. 资源优先级:壁纸图统一 loading="lazy" + fetchpriority="low",绝不与首屏正文争抢带宽;
  2. 合成层隔离:容器与每个轮播元素都带 translateZ(0)、backface-visibility: hidden、will-change: opacity(FullscreenWallpaper.astro#L141-L147),淡入淡出只触发合成器(compositor)合成,不引发重排/重绘;blur 滤镜被限制在壁纸容器自身,不会波及内容层;
  3. 零框架运行时:三段脚本全部 is:inline,不引入打包依赖,壁纸功能在纯静态页面上即可运行。

扩展点:

  • 新增图片来源:在 getImageSources() 的优先级链上插入新分支(例如本地目录扫描或 CDN 列表),保持"失败即回退"的软失败语义即可;
  • 新增可调节维度:在 overlay.switchable(或 fullscreen.switchable)加入新键,并在恢复脚本中补一条 localStorage → CSS 变量的映射;变量需同时给出配置默认值,形成"配置基线 + 用户覆盖"的双层结构;
  • 调整断点:轮播分组用 md:(768px),随机单图分组用 lg:(1024px);若要统一体验,需要同时修改 renderGroups 的 containerClass(FullscreenWallpaper.astro#L85-L98)与随机脚本中的 hidden lg:block / block lg:hidden(FullscreenWallpaper.astro#L195-L213);
  • 壁纸联动导航栏样式:src/styles/wallpaper-navbar-transparent.css 提供壁纸模式下的导航栏透明样式,属于独立样式层,可在主题层覆写。

Sources

(3 files)
src/components/features/settings
src/components/misc