Avatar

CaiCai

INITIALIZING SYSTEM

云端杂谈

代码、学术、提瓦特与泰拉大陆的碎片记录

cover
平静
2026-07-16 17:04:41

全站性能优化计划

> **统一策略:前 N eager · 后 lazy · 滑动窗口预加载 · 并发 3** --- ## 📷 照片墙 > 100 相册 × 3 图 = 300 张 · 3列 × 2行 = 一屏 6 个 **❌ 改前:** 全量预加载——前 6 个 eager 后,slice(6) 把剩余 94 个全丢进队列,282 个 `new Image()`。 **✅ 改后:** 滑动窗口——只预加载接下来 12 个(36 张),其余 lazy。 | 相册 | 策略 | |------|------| | 1 ~ 6 | eager 直接显示 | | 7 ~ 18 | 预加载 36 张(后台 `new Image`) | | 19 ~ 100 | lazy 不到不下 | > **参数:** eager=6 · preload=12 · 并发=3 · rootMargin=800px · 仅改 1 文件 --- ## ✈️ 旅行 > 垂直时间线 · 单列 · 一屏 ≈ 3 个卡片 · 每卡 1 封面 **❌ 改前:** 27 张封面全 eager · 所有卡片全 DOM 绘制 · 0.5s。 **✅ 改后:** content-visibility 跳过离屏绘制 + 图片分层。 | | 1~3 | 4~6 | 7~27 | |------|:--:|:--:|:--:| | 封面图 | eager | 预加载 | lazy | | DOM | 绘制 | 跳过 | content-visibility:auto | > ⏱ **0.5s → 0.3s** > **参数:** eager=3 · preload=3 · content-visibility:auto · 改 3 文件 --- ## 🎬 动漫 > Grid 网格 + 分页 · 每页 10 张海报 · 5 列布局 **❌ 改前:** 假翻页——100 个卡片全在 DOM,CSS hidden 隐藏,100 张同时下载。 **✅ 改后:** 真翻页 `slice`——只渲染当前页 10 张,effect 预加载下一页。 | 第 1 页 | 其余页 | |------|------| | eager 10 张立刻看 | 不渲染 · 点"下一页"秒出 | > **参数:** 真翻页 slice · 预加载下一页 · 仅改 1 文件 --- ## 💬 杂谈 > grid 网格 3 列 · 100 条 · 每条 1 封面 · 视口仅可见 1 行 **❌ 改前:** columns 瀑布流 + 100 卡片全量渲染 + PageTransition 卡顿。 **✅ 改后:** columns → grid + 分批渲染 + 图片预加载。 **① 布局:** columns 先列后行 → grid 先行后列,新卡片只从底部追加,零闪变。 **② 分批渲染:** | 时间 | 卡片 | |------|------| | 0s | 1 ~ 3(一行) | | 600ms | 4 ~ 6 | | 632ms | 7 ~ 9 | | ... | 每 32ms +3 张 | | ~1.3s | 全部 100 到位 | **③ 图片:** | 封面 | 策略 | |------|------| | 1 ~ 3 | eager | | 4 ~ 9 | 预加载 | | 10 ~ 100 | lazy + Observer | > **试错记录:** content-visibility(columns 不兼容)→ opacity 隐藏(透明元素穿插列中)→ 全量渲染(动画卡)→ **grid + 分批 ✅** > **参数:** INITIAL=3 · BATCH=3 · DELAY=600ms · 双 RAF · 仅改 1 文件 --- ## ⏳ 创作(时间线) > 交替 TimelineNode · 单列 · 一屏 ≈ 3 个卡片 **❌ 改前:** 100 个 TimelineNode 全量绘制 + 全 eager + whileInView × 100。 **✅ 改后:** 同旅行页方案。 | | 1~3 | 4~6 | 7~100 | |------|:--:|:--:|:--:| | 封面图 | eager | 预加载 | lazy | | DOM | 绘制 | content-visibility 跳过 | content-visibility 跳过 | > **参数:** eager=3 · preload=3 · content-visibility:auto · rootMargin=800px · 改 2 文件 --- ## 🧪 灵境 > 炼金术师档案 + 药瓶书架 · 100 篇文章 ≈ 13 层书架 · 每层 8 药瓶 + SVG 动画 **❌ 改前:** - `mounted=false` → 加载中 → `useEffect` → `setMounted(true)` → 内容延迟 0.5s - 13 层书架全量渲染 → PageTransition 期间每帧重绘全部 SVG → 掉帧 **✅ 改后:** **① 去 mounted:** `useState(false)` → `useState(true)`,首帧直接渲染,消灭 0.5s 白屏。 **② 书架分批:** | 时间 | 书架 | |------|------| | 0s | 1 层 | | 600ms | 3 层 | | 700ms | 5 层 | | 800ms | 7 层 | | ~1.1s | 13 层全部 | 月份切换 → 自动重置为 1 层 → 重新分批 > **参数:** INITIAL=1 · BATCH=2 · DELAY=600ms · 间隔=100ms · 改 2 文件 > ✅ 同时修复 DijiangModel(帝江号舰船)相同的 mounted 延迟 --- ## 📊 总结 | 页面 | 核心策略 | 文件 | |------|---------|:--:| | 📷 照片墙 | 滑动窗口预加载 | 1 | | ✈️ 旅行 | content-visibility + eager/lazy | 3 | | 🎬 动漫 | 真翻页 | 1 | | 💬 杂谈 | columns→grid + 分批渲染 | 1 | | ⏳ 创作 | content-visibility + 预加载 | 2 | | 🧪 灵境 | 删 mounted + 书架分批 | 2 | ‍
#bug修复
cover
开心
2026-07-05 08:40:12

图片闪烁修复总结

首次完全加载后,短时间内再次点击导航进入动漫、照片墙或杂谈页面,封面图片会**闪一下灰色背景再显示**,或者出现"重新加载"的情况。 ## **2. 根因** ### **2.1 图片过大 → 浏览器解码缓存不稳定** **位置文件原大小解码位图 (RGBA)**照片墙封面`6a33ba160508c.jpg`**3.1 MB**~12 MB杂谈封面`6a42043d3eef3.png`**1.2 MB**~4 MB 浏览器内存压力大时,解码缓存(decoded bitmap)优先被淘汰。重新挂载时即使 HTTP 缓存命中,仍需重新解码——550KB 图片解码 <1ms,3.1MB 图片解码需 15-50ms。 ### **2.2 页面导航 → 组件卸载重挂载 → 新 <img> 元素** Next.js App Router 客户端导航时,页面组件完全卸载,所有 `<img>` DOM 元素销毁。即使 `loadedSet` 记录"此 URL 已加载过",新创建的 img 仍需要解码时间。 ### **2.3 无解码缓存保护** 原 FadeImage 组件不保留任何 JS 引用,浏览器可以在组件卸载后回收解码位图。 ## **3. 修复方案** ### **3.1 代码层 —** `components/FadeImage.tsx`**(共享组件,全局生效)** **改动代码作用**同步解码`decoding={loaded ? "sync" : "async"}`已缓存图片同帧绘制,消除解码间隙图片引用保持`retainedImages` Map + FIFO 30 张持有已解码 img DOM 引用,浏览器无法回收更快过渡`duration-500` → `duration-200`首次加载淡入 200ms ### **3.2 样式层 — 背景色调优** **文件原始修改后**`AnimeClient.tsx` 封面容器`bg-slate-200 dark:bg-slate-700bg-slate-100 dark:bg-slate-800PhotoWallClient.tsx` 后层`bg-slate-300 dark:bg-slate-700bg-slate-200 dark:bg-slate-800PhotoWallClient.tsx` 中层`bg-slate-200 dark:bg-slate-600bg-slate-100 dark:bg-slate-700PhotoWallClient.tsx` 前层`dark:bg-slate-200dark:bg-slate-700` ### **3.3 数据层 — 图片瘦身** **位置旧 URL新大小缩减**照片墙封面`6a33ba160508c.jpg`**550 KB**-82%照片墙 photo[0]`6a33b8f1bb035.jpg`**445 KB**—照片墙 photo[1]`6a33b9085314e.jpg`**380 KB**—照片墙 photo[2]`6a33b9113bb2c.jpg`**828 KB**—杂谈封面`6a42043d3eef3.png`**80 KB**-93% ## **4. 防护机制** **第一层** — `loadedSet` (模块级 Set)  记录已加载 URL → 重挂载时跳过 opacity-0 → 直接可见 **第二层** — `retainedImages` Map  持有已解码 <img> DOM 引用 → 浏览器无法回收解码位图  FIFO 上限 30 张,防止内存泄漏 **第三层** — `decoding="sync"` + `duration-200` + 灰色占位  同步解码 + 快速淡入 + 一致的加载状态兜底 ## **5. 修改文件清单** **文件改动类型**`components/FadeImage.tsx`代码 — 核心修复`app/anime/AnimeClient.tsx`样式 — 背景色`app/photowall/PhotoWallClient.tsx`样式 — 背景色`data/albums.ts`数据 — 图片 URL 替换`chatters/chatter_1782710072.md`数据 — 封面 URL 替换 ## **6. 同步状态** **环境路径状态**XHBlogs 测试`新建文件夹 (2)\XinghuisamaBlogs\XHBlogs\`**已同步**XHBlogs 正式`Desktop\XinghuisamaBlogs\XHBlogs\`**已同步**my-blog-manager`新建文件夹 (2)\XinghuisamaBlogs\my-blog-manager\`**已同步**
#bug修复
cover
Emo
2026-06-30 11:03:36

照片墙图片加载优化— 修复总结

``` function PhotoImg({ src, alt, className, imgClassName, showSkeleton: _showSkeleton }) { return ( <div className={`relative overflow-hidden ${className ?? ''}`}> <img src={src} alt={alt ?? ''} decoding="async" className={`w-full ${imgClassName ?? ''}`} /> </div> ); } ``` ### 页面级预加载(7 行) ``` useEffect(() => { albums.forEach(album => { new window.Image().src = album.cover; album.photos.slice(0, 3).forEach(p => { new window.Image().src = p.url; }); }); }, []); ``` ## 改动对比 **项目原始版最终版**PhotoImg单层 `<img>`单层 `<img decoding="async">`模糊层无无淡入动画无无状态管理无无预加载无`useEffect` 预加载所有封面CSS 冗余`animate-img-reveal`已删除 ## 技术要点 ### decoding="async" 告诉浏览器在后台线程解码图片,不阻塞主线程。页面过渡动画不受图片解码干扰。 ### 页面级预加载 PhotoWallClient 挂载时用 `new Image().src` 预加载所有相册封面和前 3 张照片,后续 PhotoImg 渲染时图片数据大概率已在浏览器缓存中。 ### 架构极简 不引入模糊层、淡入动画、ref 回调、状态切换等任何复杂逻辑。浏览器原生行为处理缓存命中和渐进加载,零额外状态、零额外渲染。 ## 迭代试错总结 **尝试结果**`img.decode()` 离屏预解码DOM `<img>` 仍需重解码双层图片 (模糊 + 清晰)水波纹闪烁、灰底CSS blur-sm 模糊遮罩GPU 负担导致动画卡顿CSS transition-opacity 淡入白底/黑底透出CSS animation animate-img-reveal缓存命中时仍有闪烁`useLayoutEffect` 缓存检测仍无法消除首帧闪烁`useState` 初始化缓存检测检测不可靠,未命中时显示灰底backdrop-blur父容器白底挡住无效shimmer 骨架用户认为是灰底CSS 渐变占位用户认为是灰底Canvas 重编码基线 JPEG绕过缓存,动画变卡 **核心教训:** * 缓存命中的首帧渲染无法中途取消,任何基于 ref callback 的缓存检测都会导致首帧闪烁 * 双层图片方案让模糊层和清晰图同时可见一瞬间,产生水波纹 * CSS `filter:blur()` 多图同时渲染时 GPU 负担过重,导致页面过渡动画掉帧 * **最简方案反而是最鲁棒的**——单层 `<img>` + `decoding="async"` + 页面预加载 ## 性能特征 **缓存命中**浏览器原生即时渲染,0ms 延迟**未命中**渐进式 JPEG 逐通道解码,`decoding="async"` 保证不阻塞动画**页面过渡**无额外 GPU 负担(无 blur filter),动画流畅**预加载**`useEffect` 在挂载后立即启动,PhotoImg 渲染时数据大概率已缓存
#bug修复