feat: 支持图片渐进式加载并添加控制开关 - #473
Conversation
✅ Deploy Preview for demo-firefly ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
审阅者指南通过升级 LQIP 流水线以使用真实的低分辨率 base64 缩略图,将其接入 Astro 图片组件,添加带有持久化设置的用户开关,并基于文档级数据属性应用模糊/过渡效果,同时在页面早期初始化以避免闪烁,从而实现可配置的渐进式图片加载。 渐进式图片加载切换与应用的时序图sequenceDiagram
actor User
participant DisplaySettingsIntegrated as DisplaySettingsIntegrated
participant SettingUtils as setting_utils_ts
participant Document as document_html
participant Layout as Layout_astro
User->>DisplaySettingsIntegrated: click progressiveLoading button
DisplaySettingsIntegrated->>DisplaySettingsIntegrated: toggleProgressiveLoading()
DisplaySettingsIntegrated->>SettingUtils: setProgressiveLoadingEnabled(enabled)
SettingUtils->>SettingUtils: localStorage.setItem("progressiveLoadingEnabled", String(enabled))
SettingUtils->>Document: document.documentElement.setAttribute(data-progressive-loading, String(enabled))
SettingUtils->>Document: new CustomEvent("progressiveLoadingToggle")
Note over Layout,Document: On initial load
Layout->>Document: localStorage.getItem("progressiveLoadingEnabled")
Layout->>Document: document.documentElement.setAttribute(data-progressive-loading, progressiveEnabled)
Layout->>SettingUtils: initProgressiveLoading()
SettingUtils->>Document: document.documentElement.setAttribute(data-progressive-loading, enabled)
文件级变更
提示与命令与 Sourcery 交互
自定义你的体验访问你的控制面板 以:
获取帮助Original review guide in EnglishReviewer's GuideImplements configurable progressive image loading by upgrading the LQIP pipeline to use real low-res base64 thumbnails, wiring them into Astro image components, adding a user-facing toggle with persisted settings, and applying blur/transition effects based on a document-level data attribute initialized early to avoid flicker. Sequence diagram for progressive image loading toggle and applicationsequenceDiagram
actor User
participant DisplaySettingsIntegrated as DisplaySettingsIntegrated
participant SettingUtils as setting_utils_ts
participant Document as document_html
participant Layout as Layout_astro
User->>DisplaySettingsIntegrated: click progressiveLoading button
DisplaySettingsIntegrated->>DisplaySettingsIntegrated: toggleProgressiveLoading()
DisplaySettingsIntegrated->>SettingUtils: setProgressiveLoadingEnabled(enabled)
SettingUtils->>SettingUtils: localStorage.setItem("progressiveLoadingEnabled", String(enabled))
SettingUtils->>Document: document.documentElement.setAttribute(data-progressive-loading, String(enabled))
SettingUtils->>Document: new CustomEvent("progressiveLoadingToggle")
Note over Layout,Document: On initial load
Layout->>Document: localStorage.getItem("progressiveLoadingEnabled")
Layout->>Document: document.documentElement.setAttribute(data-progressive-loading, progressiveEnabled)
Layout->>SettingUtils: initProgressiveLoading()
SettingUtils->>Document: document.documentElement.setAttribute(data-progressive-loading, enabled)
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - 我发现了 3 个问题,并给出了一些高层次的反馈:
- 渐进式加载(progressive loading)的初始化逻辑现在分别存在于
Layout.astro中的内联脚本和setting-utils中的initProgressiveLoading,建议将它们整合到一个单独的 helper 中,以避免未来出现行为差异,并确保默认值和属性设置保持同步。 - 在
setProgressiveLoadingEnabled中,window.dispatchEvent在调用时没有防护window为 undefined 的情况;增加一个typeof window !== "undefined"检查可以让它在非浏览器或预渲染环境中更加安全。 - 新增的
getLqipStyle重新实现了已经在getLqipGradient中存在的 LQIP key 查找逻辑;通过重构将这部分逻辑抽成共享的查找 helper,可以减少重复并确保渐变模式和 base64 模式之间的行为一致。
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The progressive loading initialization logic is now split between the inline script in `Layout.astro` and `initProgressiveLoading` in `setting-utils`; consider consolidating this into a single helper to avoid future divergence and ensure the default value and attribute setting stay in sync.
- In `setProgressiveLoadingEnabled`, `window.dispatchEvent` is called without guarding against `window` being undefined; adding a `typeof window !== "undefined"` check would make this safe in non-browser or prerendered contexts.
- The new `getLqipStyle` reimplements LQIP key lookup logic that already exists in `getLqipGradient`; refactoring to share a common lookup helper would reduce duplication and keep behavior consistent between gradient and base64 modes.
## Individual Comments
### Comment 1
<location path="src/components/common/CoverImage.astro" line_range="159-161" />
<code_context>
>
<!-- LQIP 渐变占位 -->
- <div class="lqip-placeholder absolute inset-0 pointer-events-none" style={lqipProps.style} aria-hidden="true"></div>
+ <div
+ class="lqip-placeholder absolute inset-0 pointer-events-none"
+ style={`${lqipProps.style};${tinyImgUrl ? `--tiny-src: url(${tinyImgUrl})` : ''}`}
+ data-tiny-src={tinyImgUrl ? "true" : undefined}
+ aria-hidden="true"
</code_context>
<issue_to_address>
**issue (bug_risk):** 在构建 style 字符串时,需要防范 `lqipProps.style` 为 undefined。
在当前的模板字符串写法中,如果 `lqipProps.style` 为 `undefined`,将会渲染成 `style="undefined;..."`,而之前 Astro 会完全省略该属性。为避免这种情况,可以对 `lqipProps.style` 做归一化处理或按条件构建 style,例如 `const baseStyle = lqipProps.style ?? ''`,然后 `style={tinyImgUrl ? `${baseStyle};--tiny-src: url(${tinyImgUrl})` : baseStyle}`。
</issue_to_address>
### Comment 2
<location path="src/components/common/ImageWrapper.astro" line_range="119-121" />
<code_context>
+}
---
<div
</code_context>
<issue_to_address>
**issue (bug_risk):** 在没有 LQIP style 时,避免输出 `style="undefined;..."`。
与 `CoverImage.astro` 中的情况类似,如果 `lqipProps.style` 未设置,将会渲染成 `style="undefined;..."`,这与之前 `style={lqipProps.style}` 的行为不同。建议将 `lqipProps.style` 归一化为空字符串,或者仅在存在基础 style 时才添加 `style` 属性,以避免出现非预期的内联样式。
</issue_to_address>
### Comment 3
<location path="src/utils/setting-utils.ts" line_range="1304-1313" />
<code_context>
+ return stored === "true";
+}
+
+export function setProgressiveLoadingEnabled(enabled: boolean): void {
+ if (
+ typeof localStorage === "undefined" ||
+ typeof localStorage.setItem !== "function"
+ ) {
+ return;
+ }
+ localStorage.setItem("progressiveLoadingEnabled", String(enabled));
+ if (typeof document !== "undefined") {
+ document.documentElement.setAttribute(
+ "data-progressive-loading",
+ String(enabled),
+ );
+ }
+ window.dispatchEvent(
+ new CustomEvent("progressiveLoadingToggle", { detail: { enabled } }),
+ );
</code_context>
<issue_to_address>
**issue (bug_risk):** 在非浏览器/SSR 环境中,需要对 `window.dispatchEvent` 的使用进行防护。
该 helper 已经对 `localStorage` 和 `document` 做了防护,但 `window.dispatchEvent` 仍然是无条件调用,这会在 SSR 或无 window 的环境中抛出异常。请以类似方式增加防护(例如 `if (typeof window !== 'undefined') { ... }`),以便在共享代码路径中也能安全使用。
</issue_to_address>帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据这些反馈改进后续的代码评审。
Original comment in English
Hey - I've found 3 issues, and left some high level feedback:
- The progressive loading initialization logic is now split between the inline script in
Layout.astroandinitProgressiveLoadinginsetting-utils; consider consolidating this into a single helper to avoid future divergence and ensure the default value and attribute setting stay in sync. - In
setProgressiveLoadingEnabled,window.dispatchEventis called without guarding againstwindowbeing undefined; adding atypeof window !== "undefined"check would make this safe in non-browser or prerendered contexts. - The new
getLqipStylereimplements LQIP key lookup logic that already exists ingetLqipGradient; refactoring to share a common lookup helper would reduce duplication and keep behavior consistent between gradient and base64 modes.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The progressive loading initialization logic is now split between the inline script in `Layout.astro` and `initProgressiveLoading` in `setting-utils`; consider consolidating this into a single helper to avoid future divergence and ensure the default value and attribute setting stay in sync.
- In `setProgressiveLoadingEnabled`, `window.dispatchEvent` is called without guarding against `window` being undefined; adding a `typeof window !== "undefined"` check would make this safe in non-browser or prerendered contexts.
- The new `getLqipStyle` reimplements LQIP key lookup logic that already exists in `getLqipGradient`; refactoring to share a common lookup helper would reduce duplication and keep behavior consistent between gradient and base64 modes.
## Individual Comments
### Comment 1
<location path="src/components/common/CoverImage.astro" line_range="159-161" />
<code_context>
>
<!-- LQIP 渐变占位 -->
- <div class="lqip-placeholder absolute inset-0 pointer-events-none" style={lqipProps.style} aria-hidden="true"></div>
+ <div
+ class="lqip-placeholder absolute inset-0 pointer-events-none"
+ style={`${lqipProps.style};${tinyImgUrl ? `--tiny-src: url(${tinyImgUrl})` : ''}`}
+ data-tiny-src={tinyImgUrl ? "true" : undefined}
+ aria-hidden="true"
</code_context>
<issue_to_address>
**issue (bug_risk):** Guard against `lqipProps.style` being undefined when building the style string.
With the current template literal, an `undefined` `lqipProps.style` will render as `style="undefined;..."`, whereas previously Astro omitted the attribute entirely. To avoid this, normalize `lqipProps.style` or build the style conditionally, e.g. `const baseStyle = lqipProps.style ?? ''` and then `style={tinyImgUrl ? `${baseStyle};--tiny-src: url(${tinyImgUrl})` : baseStyle}`.
</issue_to_address>
### Comment 2
<location path="src/components/common/ImageWrapper.astro" line_range="119-121" />
<code_context>
+}
---
<div
</code_context>
<issue_to_address>
**issue (bug_risk):** Avoid emitting `style="undefined;..."` when no LQIP style is available.
As in `CoverImage.astro`, if `lqipProps.style` is unset this will render `style="undefined;..."`, which differs from the previous `style={lqipProps.style}` behavior. Consider normalizing `lqipProps.style` to an empty string or only adding the `style` attribute when a base style is present to avoid unintended inline styles.
</issue_to_address>
### Comment 3
<location path="src/utils/setting-utils.ts" line_range="1304-1313" />
<code_context>
+ return stored === "true";
+}
+
+export function setProgressiveLoadingEnabled(enabled: boolean): void {
+ if (
+ typeof localStorage === "undefined" ||
+ typeof localStorage.setItem !== "function"
+ ) {
+ return;
+ }
+ localStorage.setItem("progressiveLoadingEnabled", String(enabled));
+ if (typeof document !== "undefined") {
+ document.documentElement.setAttribute(
+ "data-progressive-loading",
+ String(enabled),
+ );
+ }
+ window.dispatchEvent(
+ new CustomEvent("progressiveLoadingToggle", { detail: { enabled } }),
+ );
</code_context>
<issue_to_address>
**issue (bug_risk):** Guard `window.dispatchEvent` usage for non-browser/SSR environments.
This helper guards `localStorage` and `document` but calls `window.dispatchEvent` unconditionally, which will throw in SSR/non-window environments. Please gate the dispatch similarly (e.g. `if (typeof window !== 'undefined') { ... }`) so it’s safe in shared code paths.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| export function setProgressiveLoadingEnabled(enabled: boolean): void { | ||
| if ( | ||
| typeof localStorage === "undefined" || | ||
| typeof localStorage.setItem !== "function" | ||
| ) { | ||
| return; | ||
| } | ||
| localStorage.setItem("progressiveLoadingEnabled", String(enabled)); | ||
| if (typeof document !== "undefined") { | ||
| document.documentElement.setAttribute( |
There was a problem hiding this comment.
issue (bug_risk): 在非浏览器/SSR 环境中,需要对 window.dispatchEvent 的使用进行防护。
该 helper 已经对 localStorage 和 document 做了防护,但 window.dispatchEvent 仍然是无条件调用,这会在 SSR 或无 window 的环境中抛出异常。请以类似方式增加防护(例如 if (typeof window !== 'undefined') { ... }),以便在共享代码路径中也能安全使用。
Original comment in English
issue (bug_risk): Guard window.dispatchEvent usage for non-browser/SSR environments.
This helper guards localStorage and document but calls window.dispatchEvent unconditionally, which will throw in SSR/non-window environments. Please gate the dispatch similarly (e.g. if (typeof window !== 'undefined') { ... }) so it’s safe in shared code paths.
|
这个缩略图生成可以跟og一样,加个开关,默认关闭,因为会影响构建速度。 还有没必要把这东西往前台设置面板那里加。 |
… loading frontend settings
|
删除了前端的设置面板上的开关,现在是代码控制了,默认关闭 |





Type of change
Checklist
Changes
参考链接: 图片渐进式加载
本 PR 实现了图片的渐进式加载功能。在封面图和图片包装组件完全加载前,会优先展示一个经过高斯模糊处理的低分辨率占位图,以提升首屏视觉体验和感知加载速度。(后面看了一下实际效果,发现其实已经实现过了,渐变过渡,算是增强版本?)
具体变更如下:
scripts/generate-lqips.ts,将原来基于 2x2 像素提取的 18 位十六进制渐变色占位图,改为了使用 Sharp 生成的16x16真实低清 base64 占位图。src/utils/lqip-utils.ts,完美兼容旧版的 18 位渐变色格式与新版的 base64 占位图格式。CoverImage.astro与ImageWrapper.astro中,通过 Astro 的getImage动态生成 20px 宽度、20% 质量的本地微缩图,并通过 CSS 变量--tiny-src注入。src/styles/main.css中添加了模糊效果(20px)、防白边的微调缩放(1.05)以及平滑的淡入淡出过渡动画。DisplaySettingsIntegrated.svelte)中,新增了“图片设置”区块以及“图片渐进式加载”开关,并将用户偏好持久化至localStorage。Layout.astro的 HTML 渲染前读取缓存配置并设置data-progressive-loading属性,避免页面加载时出现白屏或闪烁。progressiveLoading、imageSettings)。How To Test
pnpm dev)或进行构建预览(pnpm build && pnpm preview)。Screenshots (if applicable)
Summary by Sourcery
引入可配置的渐进式图片加载功能,使用微小的模糊占位图并更新 LQIP 支持。
New Features:
Enhancements:
Documentation:
Original summary in English
Summary by Sourcery
Introduce configurable progressive image loading using tiny blurred placeholders and updated LQIP support.
New Features:
Enhancements:
Documentation: