视觉特效与全屏壁纸
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 主题的视觉基调能力。它解决三个问题:
- 氛围渲染:在桌面端与移动端分别提供壁纸图集(
/assets/desktop-banner/、/assets/mobile-banner/),支持多图轮播或随机单图; - 可读性控制:壁纸不能压过正文,因此壁纸容器默认
opacity: 0.8、blur: 1px、z-index: -1,并且pointer-events-none,永远不会拦截用户交互; - 用户偏好:三种模式(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
分层意图说明:
- 配置 → 渲染是单向数据流:
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() 实现了一条明确的优先级链:
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();设计要点:
- API 覆盖是软失败:
fetch失败或返回空列表时只console.warn,随后回退到本地配置。壁纸是装饰性资源,构建期宁可降级也不让页面构建失败。 - API 图源按换行符分隔:响应体按
\n拆分并过滤空行,这是常见的"每行一个 URL"随机图 API 约定;命中后 desktop 与 mobile 共用同一列表。 - desktop / mobile 双向回退:对象型
src下,desktop 为空用 mobile 补,反之亦然;字符串或字符串数组则两端共用。注意config.src的类型在类型层是宽松的,运行时由这段归一化代码兜底。 - 完全无图则不渲染:
if (!hasDesktopImages && !hasMobileImages) return null;(FullscreenWallpaper.astro#L57-L59),保证配置为空时不会输出一个空的 fixed 容器。
响应式分组与轮播判定
解析出的两组图片被打包为 renderGroups,分别套上 hidden md:block(桌面,≥768px 显示)与 block md:hidden(移动)的容器类。轮播开启条件是 carousel.enable 且任一设备组图片数大于 1:
1 // 轮播配置
2 const isCarouselEnabled =
3 config.carousel?.enable &&
4 (imageSources.desktop.length > 1 || imageSources.mobile.length > 1);
5 const carouselInterval = config.carousel?.interval || 5;轮播分支中每张图都通过 Image 原子组件输出,并针对设备做了差异化优化:
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 />widths 与 sizes="100vw" 声明壁纸铺满视口;移动端宽度档位更低且质量乘以 0.9,是对移动网络与 DPR 的带宽让步。loading="lazy" + fetchpriority="low" 明确表达"壁纸不与首屏内容争抢资源"的意图。
容器样式与 CSS 变量契约
壁纸容器是一个 fixed 覆盖层,其视觉参数全部挂到 CSS 变量上,默认值来自配置:
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>三个关键决定:
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 流中并在图片渲染前同步执行:
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>这段脚本揭示了两条隐含契约,扩展时必须遵守:
overlaySwitchable是一个"白名单":config.overlay.switchable可以是布尔(全开/全关)或逐项对象{ opacity, blur, cardOpacity }。为false的项即使localStorage里有旧值也不会被读取——防止用户关闭"可调节透明度"后旧偏好仍然生效。- 非 overlay 模式直接隐藏而非移除:
display: none会停掉合成与绘制,但不销毁 DOM,切回 overlay 时无需重新解析图片。同时它会移除--card-transparent-opacity,让卡片透明度回到主题默认值。
注意脚本通过 document.currentScript.parentElement 定位到壁纸容器自身,而不是查询选择器——因为同一页面理论上可能有多个壁纸实例(例如布局与页眉各自渲染),每个实例只处理自己的样式。
交互层实现:WallpaperSwitch.svelte
设置面板中的壁纸切换按钮提供三档模式,通过 panelManager 管理浮层面板:
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}实现要点:
- 使用 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,保证同一时刻只有一个浮层面板打开(避免壁纸面板与其他设置面板重叠)。
对应的渲染部分:
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}Core Flow
下面两张图分别描述"页面加载时壁纸的初始化/恢复"与"多图轮播的运行时循环",均对应 FullscreenWallpaper.astro 中的真实代码路径。
初始化与偏好恢复(首帧)
同步执行是关键:恢复脚本位于容器开标签之后、图片元素之前,浏览器在解析到壁纸 <img> 之前就已确定 opacity/blur/display,因此用户看到的第一个画面就是最终状态。
轮播运行时循环
对应实现在 initFullscreenWallpaperCarousel()(FullscreenWallpaper.astro#L231-L334),核心循环如下:
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 }每个轮播元素本身带有 transition-opacity duration-1000(1 秒淡入淡出,见 FullscreenWallpaper.astro#L142-L147),运行时只切换 style.opacity 的 0/1,由 CSS transition 完成平滑过渡;这是"JS 管状态、CSS 管动画"的典型分工。
非轮播模式:随机单图 + 防重复
carousel 未开启(或仅一张图)时走 #wallpaper-single-container 分支,由另一段内联脚本随机挑选图片:
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');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),完整默认值如下:
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};各字段在渲染层中的实际消费方式:
| 字段 | 类型 / 默认值 | 消费位置与行为 |
|---|---|---|
enable | boolean / true | 内联恢复脚本的 wallpaperEnabled;为 false 时强制 mode = 'banner',全屏壁纸整体不可用 |
src | string | string[] | { desktop, mobile } | getImageSources() 归一化为 desktop/mobile 两组;对象形式支持各自回退 |
position | "top" | "center" | "bottom" / "center" | 映射为 Tailwind 的 object-top / object-center / object-bottom,控制图片裁剪锚点(FullscreenWallpaper.astro#L74-L83) |
carousel.enable | boolean / true | 多图时启用轮播分支;否则走随机单图分支 |
carousel.interval | number(秒)/ 5 | setInterval 周期 = interval * 1000 ms |
zIndex | number / -1 | 直接写入容器内联 z-index,保证壁纸位于内容之下 |
opacity | number / 0.8 | 作为 --wallpaper-opacity 的回退默认值(未被用户覆盖时生效) |
blur | number(px)/ 1 | 作为 --wallpaper-blur 的回退默认值 |
overlay.switchable | boolean | { 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 数值的写入与实时预览逻辑未在本次读取范围内。
浏览器存储契约汇总
壁纸能力跨组件共享以下存储键,任何扩展都必须遵守同一契约:
| 存储键 | 作用域 | 写入方 | 读取方 | 语义 |
|---|---|---|---|---|
wallpaperMode | localStorage | setWallpaperMode(setting-utils) | 内联恢复脚本、getStoredWallpaperMode | 当前壁纸模式;缺省视为 'banner' |
overlayOpacity | localStorage | 设置面板(overlay 模式) | 内联恢复脚本(受 switchable.opacity 门控) | 写入 --wallpaper-opacity |
overlayBlur | localStorage | 设置面板(overlay 模式) | 内联恢复脚本(受 switchable.blur 门控) | 写入 --wallpaper-blur(追加 px) |
overlayCardOpacity | localStorage | 设置面板(overlay 模式) | 内联恢复脚本(受 switchable.cardOpacity 门控) | 写入根元素 --card-transparent-opacity |
mizuki_wallpaper_index | sessionStorage | 轮播 switchNextImage | 轮播初始化 initImage | 当前轮播索引;会话内连续性 |
wallpaper_desktop_index | sessionStorage | 随机单图脚本 | 同一脚本 | 上次桌面随机索引,防连续重复 |
wallpaper_mobile_index | sessionStorage | 随机单图脚本 | 同一脚本 | 上次移动随机索引,防连续重复 |
选择 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兼容布尔与对象:脚本内做了归一化,布尔值会展开为三项同值,避免配置形态变化导致读取崩溃。
性能与扩展点
性能设计集中在三处:
- 资源优先级:壁纸图统一
loading="lazy"+fetchpriority="low",绝不与首屏正文争抢带宽; - 合成层隔离:容器与每个轮播元素都带
translateZ(0)、backface-visibility: hidden、will-change: opacity(FullscreenWallpaper.astro#L141-L147),淡入淡出只触发合成器(compositor)合成,不引发重排/重绘;blur滤镜被限制在壁纸容器自身,不会波及内容层; - 零框架运行时:三段脚本全部
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提供壁纸模式下的导航栏透明样式,属于独立样式层,可在主题层覆写。
Related Links
- FullscreenWallpaper.astro — 全屏壁纸渲染组件(源解析、恢复脚本、轮播运行时)
- backgroundWallpaper.ts — 壁纸配置默认值
- WallpaperSwitch.svelte — 壁纸模式切换面板
- wallpaper-navbar-transparent.css — 壁纸模式下的导航栏透明样式