@xingwangzhe/stalux 1.20.1 → 1.20.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xingwangzhe/stalux",
3
- "version": "1.20.1",
3
+ "version": "1.20.2",
4
4
  "description": "A powerful, modern Astro blog theme — use as template or install as plugin",
5
5
  "keywords": [
6
6
  "astro",
@@ -128,8 +128,8 @@ const llmPromote = llmPromoteRaw
128
128
  data-stalux-bg-index="0"
129
129
  >
130
130
  {llmPromote && <div style="display:none">{llmPromote}</div>}
131
- <div class="bg-layer bg-a" style="opacity: 1;" aria-hidden="true"></div>
132
- <div class="bg-layer bg-b" aria-hidden="true"></div>
131
+ <div class="bg-layer bg-a" style="opacity: 1;" transition:persist aria-hidden="true"></div>
132
+ <div class="bg-layer bg-b" transition:persist aria-hidden="true"></div>
133
133
  <main class="stalux-main">
134
134
  <slot />
135
135
  </main>
@@ -38,27 +38,49 @@ function setLayerOpacity(layer: "a" | "b", opacity: number): void {
38
38
  if (el) el.style.opacity = String(opacity);
39
39
  }
40
40
 
41
- function applyBackground(): void {
42
- // 客户端运行时随机选图,构建产物保持确定性
43
- const index = Math.floor(Math.random() * backgroundImages.length);
44
- const layer: "a" | "b" = "a";
45
- if (backgroundImages.length > 0 && backgroundImages[index]) {
46
- const el = getLayerEl(layer);
47
- if (el) el.style.backgroundImage = `url('${backgroundImages[index]}')`;
41
+ function getActiveLayer(): "a" | "b" {
42
+ const aOpacity = Number(getLayerEl("a")?.style.opacity ?? 0);
43
+ const bOpacity = Number(getLayerEl("b")?.style.opacity ?? 0);
44
+ return bOpacity > aOpacity ? "b" : "a";
45
+ }
46
+
47
+ function pickBackgroundIndex(previous: number): number {
48
+ if (backgroundImages.length < 2) return 0;
49
+ let index = Math.floor(Math.random() * backgroundImages.length);
50
+ while (index === previous) {
51
+ index = Math.floor(Math.random() * backgroundImages.length);
48
52
  }
49
- setLayerOpacity(layer, 1);
50
- setLayerOpacity("b", 0);
53
+ return index;
54
+ }
55
+
56
+ function crossfadeBackground(): void {
57
+ if (backgroundImages.length === 0) return;
58
+
59
+ const activeLayer = getActiveLayer();
60
+ const nextLayer: "a" | "b" = activeLayer === "a" ? "b" : "a";
61
+ const previousIndex = Number(document.body.dataset.staluxBgIndex ?? -1);
62
+ const index = pickBackgroundIndex(previousIndex);
63
+ const next = getLayerEl(nextLayer);
64
+ if (!next || !backgroundImages[index]) return;
65
+
66
+ next.style.backgroundImage = `url('${backgroundImages[index]}')`;
67
+ setLayerOpacity(nextLayer, 1);
68
+ setLayerOpacity(activeLayer, 0);
51
69
  document.body.dataset.staluxBgIndex = String(index);
52
- document.body.dataset.staluxBgLayer = layer;
70
+ document.body.dataset.staluxBgLayer = nextLayer;
71
+ }
72
+
73
+ function initializeBackground(): void {
74
+ const active = getLayerEl(getActiveLayer());
75
+ if (active?.style.backgroundImage) return;
76
+ crossfadeBackground();
53
77
  }
54
78
 
55
- // 首次加载与 VT 软导航都触发 astro:page-load:
56
- // - 首次加载:window load 后触发(router.js addEventListener("load", onPageLoad))。
57
- // - VT 软导航:swap 完成后、过渡动画期间触发(router.js onPageLoad()),
58
- // 此时设置随机背景,过渡中旧图淡出、新图淡入,无跳变闪变。
59
- // 注意:软导航不会重新执行打包的 module 脚本(监听器仍是首访注册的),
60
- // 所以这里不能做“只执行一次”的去重,每次 page-load 都必须随机;
61
- // 若软导航后新脚本被重新执行,重复监听导致随机两次也无妨(仍是随机图)。
62
- // body 的 data-stalux-bg-* 是 SSR 固定占位(a/0,为了构建确定性),
63
- // 不能再用它判断“服务端已渲染正确背景”,否则随机逻辑永不执行。
64
- document.addEventListener("astro:page-load", applyBackground);
79
+ // before-swap 在 View Transition 的快照之后、DOM 替换之前触发;背景层使用
80
+ // transition:persist 保留,因此可以在页面内容交换期间完成双层 crossfade。
81
+ // 非 VT 浏览器也会触发该生命周期,fallback 同样保持平滑。
82
+ if (!(window as Window & { __STALUX_BG_LISTENER__?: boolean }).__STALUX_BG_LISTENER__) {
83
+ (window as Window & { __STALUX_BG_LISTENER__?: boolean }).__STALUX_BG_LISTENER__ = true;
84
+ document.addEventListener("astro:before-swap", crossfadeBackground);
85
+ document.addEventListener("astro:page-load", initializeBackground);
86
+ }
@@ -263,7 +263,7 @@ ul {
263
263
  background: #2a2a2a;
264
264
  }
265
265
 
266
- /* 背景层 - 双层随机背景(不在 View Transition 中 crossfade,避免 SVG 双倍光栅化卡顿) */
266
+ /* 背景层 - 双层随机背景,在路由切换时交叉淡入淡出 */
267
267
  .bg-layer {
268
268
  position: fixed;
269
269
  inset: 0;
@@ -275,12 +275,19 @@ ul {
275
275
  filter: var(--stalux-bg-filter, none);
276
276
  pointer-events: none;
277
277
  opacity: 0;
278
+ transition: opacity 650ms ease-in-out;
278
279
  /* 禁止浏览器强制色彩模式(高对比度/暗黑模式)调整背景图颜色 */
279
280
  forced-color-adjust: none;
280
281
  /* 关键:防止 Safari/WebKit 在暗色模式下自动调整 SVG 背景图颜色 */
281
282
  color-scheme: only light;
282
283
  }
283
284
 
285
+ @media (prefers-reduced-motion: reduce) {
286
+ .bg-layer {
287
+ transition-duration: 0ms;
288
+ }
289
+ }
290
+
284
291
  /* footer 基础样式,保证页脚始终可见且不覆盖内容 */
285
292
  footer {
286
293
  padding: var(--space-lg) var(--space-md);