Skip to content

Commit 1988bff

Browse files
Caldisclaude
andcommitted
feat(core): add loadingDelay prop with anti-flicker delay-before-show
Loading indicator was synchronously set on every page change and only cleared by a 500ms polling fallback. Cached / key-reused pages flashed the spinner ~500ms then hid it — annoying visual noise especially when paging quickly through pre-loaded images. Three coordinated changes implement the industry-standard "delay-before-show" pattern (see Ant Design Spin `delay`, react-loadable `delay`, TanStack Query): 1. New public prop `loadingDelay: number` (default 200ms; range 0-1000 in playground). Defers `isFetching=true` setState by N ms; if the image loads within the window via any path (onLoad event, polling, or cdU fast-path), the delay timer is cancelled → loading indicator never shows. 2. cdU page-change fast-path: when `node.complete && naturalWidth > 0` on the new center (cached image, key-reused side→center), call `handleImageLoadEnd` directly. Skips loading state entirely, no timer needed. Combined with the scale-calibration interrupt and canZoom recompute already wired here, all three concerns now share the same "new center is ready" branch. 3. handleImageLoad (centerOnLoad path) now calls handleImageLoadEnd. Was relying solely on 500ms polling to clear loading; onLoad is the canonical "image is loaded" signal — using it directly cuts the polling-driven flash window from ~500ms to one render cycle. Behavior matrix: Cached page change → no Loading shown (fast-path) Fast load (<200ms) → no Loading shown (delay timer cancelled) Medium load (200-500ms) → Loading shown 0-300ms Slow load (>500ms) → Loading shown N-200ms (vs N-500ms before) Tests added (4 new, 112 total): - Cached page change: #zmageLoading not in DOM throughout (fast-path lock) - loadingDelay=50 prop: Loading appears around 50ms for uncached page - Fast onLoad cancels delay timer: Loading never shown - StrictMode unmount cancels loadingShowDelayTimer (cleanup invariant) Public docs synced (sync-public-docs skill, all 8 destinations): - types/global.ts InterfaceAndInteractionParams; defProp.loadingDelay - getConfigFromProps + Browser context value (single-source plumbing) - home/schema/param-schema.ts (PARAM_SCHEMA + defProp mirror) - 7 i18n files (en/de/es/fr/ja/ko/zh-CN), all 283 keys parity verified - README.md / README.zh-CN.md interface props table + Full type comment - AGENTS.md prop quick-reference Interface row - llms.txt API table row; docs/llms.txt regenerated via home build - llms-eval/eval.test.mjs new contract assertion (16/16 pass) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent d2834bf commit 1988bff

24 files changed

Lines changed: 657 additions & 395 deletions

File tree

AGENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ Single `BaseType` covers all. Grouped here by purpose:
8484
| Preset | `preset: 'desktop' \| 'mobile' \| 'auto'` | drives default `controller` / `hotKey` / `animate`. `'auto'` resolves via `matchMedia('(pointer: coarse) and (hover: none)')` on the client; SSR falls back to desktop |
8585
| Controlled | `browsing` | omit for self-managed; pair with `onBrowsing` if set |
8686
| Functional | `controller`, `hotKey`, `animate` | pass `boolean` to disable, or partial object to override. `controller.flip` and `hotKey.flip` are umbrellas over their per-side counterparts (`flipLeft` / `flipRight`); enabling the umbrella forces both sides on. `controller.backdrop` / `controller.color` decouple the toolbar bg/icon-color from the modal `backdrop` (set both when `backdrop` is solid dark) |
87-
| Interface | `hideOnScroll`, `coverVisible`, `backdrop`, `zIndex`, `radius`, `edge`, `loop`, `closeOnDoubleClick` | desktop-only flags noted in README. `closeOnDoubleClick` defaults `false`; turn on to dismiss the viewer with a dblclick on the image |
87+
| Interface | `hideOnScroll`, `coverVisible`, `backdrop`, `zIndex`, `radius`, `edge`, `loop`, `closeOnDoubleClick`, `loadingDelay` | desktop-only flags noted in README. `closeOnDoubleClick` defaults `false`; turn on to dismiss the viewer with a dblclick on the image. `loadingDelay` defaults `200ms` — anti-flicker delay before showing the loading indicator (set 0 for legacy instant-show) |
8888
| Lifecycle | `onBrowsing`, `onZooming`, `onSwitching`, `onRotating`, `onError` | first 4 callback args: `boolean`/`boolean`/`number`/`number`. `onError(e: SyntheticEvent<HTMLImageElement>)` fires for cover **or** viewer img-load failure — the only hook for the viewer-side failure (cover also flows via native `<img>` `onError` passthrough) |
8989
| Native | All `HTMLAttributes<HTMLImageElement>` | className, style, onClick, etc. transparently forwarded to inner `<img>` |
9090

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ Defaults: desktop = `{ browsing: true, flip: 'crossFade' }`, mobile = `{ browsin
242242
| `edge` | `number` | `0` | Minimum margin between image and viewport (px). |
243243
| `loop` | `boolean` | `true` | Wrap-around when paging past the ends. |
244244
| `closeOnDoubleClick` | `boolean` | `false` | Close the viewer on image double-click. Off by default; turn on to allow dismissing with a double-click. |
245+
| `loadingDelay` | `number` | `200` | Delay (ms) before showing the loading indicator. If the image loads within this window, the indicator never appears — prevents the flash on cached page changes. Set 0 for legacy instant-show. |
245246

246247
### Lifecycle
247248

@@ -270,7 +271,7 @@ export type BaseType =
270271
& BaseParams // src / alt / caption / set / defaultPage
271272
& PresetParams // preset
272273
& FunctionalParams // controller / hotKey / animate
273-
& InterfaceAndInteractionParams // hideOnScroll / coverVisible / backdrop / zIndex / radius / edge / loop / closeOnDoubleClick
274+
& InterfaceAndInteractionParams // hideOnScroll / coverVisible / backdrop / zIndex / radius / edge / loop / closeOnDoubleClick / loadingDelay
274275
& LifeCycleParams // onBrowsing / onZooming / onSwitching / onRotating / onError
275276
& ControlledParams // browsing
276277
& HTMLAttributes<HTMLImageElement>

README.zh-CN.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ interface Animate {
237237
| `edge` | `number` | `0` | 图片距屏幕边缘的留白 (px)。 |
238238
| `loop` | `boolean` | `true` | 多图模式:尾页是否循环回首页。 |
239239
| `closeOnDoubleClick` | `boolean` | `false` | 双击图片时关闭查看器。默认关闭;启用后浏览态双击图片即退出。 |
240+
| `loadingDelay` | `number` | `200` | Loading 指示器显示前的延迟 (ms)。在此期间内图片加载完成则不显示 loading,避免快速切换缓存图时的视觉闪烁。默认 200ms (业界 react-loadable 经典值);设为 0 = 立即显示 (旧行为)。 |
240241

241242
### 生命周期
242243

@@ -265,7 +266,7 @@ export type BaseType =
265266
& BaseParams // src / alt / caption / set / defaultPage
266267
& PresetParams // preset
267268
& FunctionalParams // controller / hotKey / animate
268-
& InterfaceAndInteractionParams // hideOnScroll / coverVisible / backdrop / zIndex / radius / edge / loop / closeOnDoubleClick
269+
& InterfaceAndInteractionParams // hideOnScroll / coverVisible / backdrop / zIndex / radius / edge / loop / closeOnDoubleClick / loadingDelay
269270
& LifeCycleParams // onBrowsing / onZooming / onSwitching / onRotating / onError
270271
& ControlledParams // browsing
271272
& HTMLAttributes<HTMLImageElement>

docs/404.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@
7272
document.documentElement.classList.toggle('dark', theme === 'dark')
7373
} catch {}
7474
</script>
75-
<script type="module" crossorigin src="/assets/index-CUsU0GPn.js"></script>
76-
<link rel="stylesheet" crossorigin href="/assets/index-xvpohMzA.css">
75+
<script type="module" crossorigin src="/assets/index-DsrOIYKm.js"></script>
76+
<link rel="stylesheet" crossorigin href="/assets/index-ButVaVSj.css">
7777
</head>
7878
<body>
7979
<div id="app"></div>
Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/assets/index-CUsU0GPn.js

Lines changed: 0 additions & 377 deletions
This file was deleted.

docs/assets/index-DsrOIYKm.js

Lines changed: 377 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/index.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,8 +72,8 @@
7272
document.documentElement.classList.toggle('dark', theme === 'dark')
7373
} catch {}
7474
</script>
75-
<script type="module" crossorigin src="/assets/index-CUsU0GPn.js"></script>
76-
<link rel="stylesheet" crossorigin href="/assets/index-xvpohMzA.css">
75+
<script type="module" crossorigin src="/assets/index-DsrOIYKm.js"></script>
76+
<link rel="stylesheet" crossorigin href="/assets/index-ButVaVSj.css">
7777
</head>
7878
<body>
7979
<div id="app"></div>

docs/llms.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ const [open, setOpen] = useState(false)
7171
| `zIndex` / `radius` / `edge` | `number` | `1000` / `0` / `0` | Modal stacking, image corner radius, viewport padding. |
7272
| `loop` | `boolean` | `true` | Wrap-around when paging past the ends. |
7373
| `closeOnDoubleClick` | `boolean` | `false` | Close the viewer on image double-click. Off by default to preserve current single-click semantics; turn on to dismiss with a double-click. |
74+
| `loadingDelay` | `number` | `200` | Delay (ms) before showing the loading indicator. If the image loads within this window, the indicator never appears — prevents the flash on cached page changes. Set 0 for legacy instant-show. |
7475
| `onBrowsing` | `(isBrowsing: boolean) => void` | — | Fires on open/close. |
7576
| `onZooming` | `(isZooming: boolean) => void` | — | Fires on zoom in/out. |
7677
| `onSwitching` | `(page: number) => void` | — | Fires on page change. |

llms.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ const [open, setOpen] = useState(false)
7171
| `zIndex` / `radius` / `edge` | `number` | `1000` / `0` / `0` | Modal stacking, image corner radius, viewport padding. |
7272
| `loop` | `boolean` | `true` | Wrap-around when paging past the ends. |
7373
| `closeOnDoubleClick` | `boolean` | `false` | Close the viewer on image double-click. Off by default to preserve current single-click semantics; turn on to dismiss with a double-click. |
74+
| `loadingDelay` | `number` | `200` | Delay (ms) before showing the loading indicator. If the image loads within this window, the indicator never appears — prevents the flash on cached page changes. Set 0 for legacy instant-show. |
7475
| `onBrowsing` | `(isBrowsing: boolean) => void` | — | Fires on open/close. |
7576
| `onZooming` | `(isZooming: boolean) => void` | — | Fires on zoom in/out. |
7677
| `onSwitching` | `(page: number) => void` | — | Fires on page change. |

0 commit comments

Comments
 (0)