Skip to content

fix: eliminate image flicker by gating opacity on fit-transform readiness#1370

Open
siddharthsai218 wants to merge 4 commits into
AOSSIE-Org:mainfrom
siddharthsai218:image-flicker
Open

fix: eliminate image flicker by gating opacity on fit-transform readiness#1370
siddharthsai218 wants to merge 4 commits into
AOSSIE-Org:mainfrom
siddharthsai218:image-flicker

Conversation

@siddharthsai218

@siddharthsai218 siddharthsai218 commented Jul 9, 2026

Copy link
Copy Markdown

Addressed Issues:

Fixes #1366

Additional Notes:

Description

Fixes the image flicker at the top-left corner during slideshow/navigation transitions.

Root Cause

The image became visible before its fit/zoom transform had actually been applied. There were two gaps:

  1. Image rendered at natural size/position briefly before dimensions were calculated on load
  2. Even after dimensions were known, the fit transform applied one frame later via requestAnimationFrame, causing a visible flash at the untransformed (top-left) position

Fix

Added an isFitReady state that only becomes true once the fit transform has actually been applied. Image opacity is now gated on this instead of just contentDimensions, so the image stays hidden until it's fully positioned and scaled correctly — eliminating the top-left flicker.

Changes

  • useZoomTransform.ts: added isFitReady state, set/reset logic, exposed from hook
  • ZoomableImage.tsx: gated image opacity/transition on isFitReady

Minimal, modular change — ~5 lines across 2 files, no unrelated modifications.

Testing

Confirmed the top-left flicker is eliminated across multiple transitions, rapid navigation, and slideshow mode.

AI Usage Disclosure:

We encourage contributors to use AI tools responsibly when creating Pull Requests. While AI can be a valuable aid, it is essential to ensure that your contributions meet the task requirements, build successfully, include relevant tests, and pass all linters. Submissions that do not meet these standards may be closed without warning to maintain the quality and integrity of the project. Please take the time to understand the changes you are proposing and their impact. AI slop is strongly discouraged and may lead to banning and blocking. Do not spam our repos with AI slop.

Check one of the checkboxes below:

  • This PR does not contain AI-generated code at all.
  • This PR contains AI-generated code. I have read the AI Usage Policy and this PR complies with this policy. I have tested the code locally and I am responsible for it.

I have used the following AI models and tools: Claude

Checklist

  • My PR addresses a single issue, fixes a single bug or makes a single improvement.
  • My code follows the project's code style and conventions
  • If applicable, I have made corresponding changes or additions to the documentation
  • If applicable, I have made corresponding changes or additions to tests
  • My changes generate no new warnings or errors
  • I have joined the Discord server and I will share a link to this PR with the project maintainers there
  • I have read the Contribution Guidelines
  • Once I submit my PR, CodeRabbit AI will automatically review it and I will address CodeRabbit's comments.
  • I have filled this PR template completely and carefully, and I understand that my PR may be closed without review otherwise.

Summary by CodeRabbit

  • New Features
    • Images now remain hidden (opacity: 0) until the initial fit-to-view step completes, then fade in to opacity: 1.
  • Bug Fixes
    • Prevents flicker by avoiding any opacity transition until the image is ready; also ensures the hidden state is restored during image resets.
  • Tests
    • Added coverage to verify initial opacity: 0 with no opacity transition, then fade-in to opacity: 1 with opacity 120ms ease-out after image load.

@github-actions github-actions Bot added bug Something isn't working frontend labels Jul 9, 2026
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: cc527388-acf0-46a7-b099-957da4fae395

📥 Commits

Reviewing files that changed from the base of the PR and between 23269a1 and 57ea832.

📒 Files selected for processing (1)
  • frontend/src/hooks/useZoomTransform.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/hooks/useZoomTransform.ts

📝 Walkthrough

Walkthrough

Adds an isFitReady boolean state to useZoomTransform that tracks completion of the fit-to-view transform, resetting it on image changes and exposing it to consumers. ZoomableImage uses that flag to keep the image hidden until fit is ready, preventing the flicker that occurred during slideshow and navigation transitions.

Changes

isFitReady Flicker Fix

Layer / File(s) Summary
isFitReady state lifecycle in useZoomTransform
frontend/src/hooks/useZoomTransform.ts
Adds isFitReady state variable, sets it true after successful fit transform application and when retries are exhausted, resets it false during image-path change logic, and exposes it in the hook's returned object.
ZoomableImage opacity gating
frontend/src/components/Media/ZoomableImage.tsx, frontend/src/components/Media/__tests__/ZoomableImage.test.tsx
Destructures isFitReady from the hook and conditionally sets <img> opacity to 0 with no transition while not ready, then to 1 with a 120ms ease-out transition once ready; adds comprehensive test coverage verifying the opacity and transition states before and after the fit transform completes.

Estimated code review effort: 2 (Simple) | ~10 minutes

Suggested reviewers: rohan-pandeyy, rahulharpal1603

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly states the user-facing fix: preventing image flicker by waiting for fit-transform readiness.
Linked Issues check ✅ Passed The changes implement the requested isFitReady gating in the hook and image component, matching issue #1366.
Out of Scope Changes check ✅ Passed The diff stays focused on the flicker fix and adds only the supporting state and test coverage.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/hooks/useZoomTransform.ts (1)

196-200: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Consider a fallback for permanent invisibility when fit never succeeds.

If applyFitTransform exhausts all retry frames without success (e.g., zero-size viewport, corrupted image with no dimensions), isFitReady stays false and the image remains permanently invisible. The ResizeObserver provides a recovery path for viewport size changes, but not all scenarios are covered (e.g., no ResizeObserver support, or dimensions that never become available).

Consider adding a fallback that sets isFitReady to true after MAX_FIT_RETRY_FRAMES exhausted, so the image is at least visible (even if not perfectly fit) rather than invisible.

🛡️ Proposed fallback in scheduleFitTransform
      fitFrameRef.current = scheduleFrame(() => {
        fitFrameRef.current = null;
        const applied = applyFitTransform();

        if (!applied && fitRetryCountRef.current < MAX_FIT_RETRY_FRAMES) {
          fitRetryCountRef.current += 1;
          scheduleFitTransform(false);
+       } else if (!applied) {
+         // All retries exhausted; show image anyway to avoid permanent invisibility.
+         setIsFitReady(true);
        }
      });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@frontend/src/hooks/useZoomTransform.ts` around lines 196 - 200, In
useZoomTransform’s fit retry flow, `applyFitTransform` can leave `isFitReady`
false forever when the viewport/image never becomes measurable, causing
permanent invisibility. Update the retry handling around `scheduleFitTransform`
and the `didApply` branch so that once `MAX_FIT_RETRY_FRAMES` is exhausted you
still set `isFitReady` to true as a fallback, while keeping the existing
successful-fit state updates in `isFitInitializedRef`, `hasUserInteractedRef`,
and `fitRetryCountRef` intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@frontend/src/components/Media/ZoomableImage.tsx`:
- Around line 117-118: Add test coverage for the `isFitReady` opacity gate in
`ZoomableImage` by updating
`frontend/src/components/Media/__tests__/ZoomableImage.test.tsx` to verify the
image starts with `opacity: 0` and no transition before load, then changes to
`opacity: 1` with `transition: opacity 120ms ease-out` after the load flow
completes. Use the existing `ZoomableImage` render/load test setup and assert
against the rendered image style to cover this gating behavior.

---

Outside diff comments:
In `@frontend/src/hooks/useZoomTransform.ts`:
- Around line 196-200: In useZoomTransform’s fit retry flow, `applyFitTransform`
can leave `isFitReady` false forever when the viewport/image never becomes
measurable, causing permanent invisibility. Update the retry handling around
`scheduleFitTransform` and the `didApply` branch so that once
`MAX_FIT_RETRY_FRAMES` is exhausted you still set `isFitReady` to true as a
fallback, while keeping the existing successful-fit state updates in
`isFitInitializedRef`, `hasUserInteractedRef`, and `fitRetryCountRef` intact.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: c899269a-99cb-4f70-9397-6f693d00e25c

📥 Commits

Reviewing files that changed from the base of the PR and between ffc0d48 and e5b7213.

📒 Files selected for processing (2)
  • frontend/src/components/Media/ZoomableImage.tsx
  • frontend/src/hooks/useZoomTransform.ts

Comment thread frontend/src/components/Media/ZoomableImage.tsx
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working frontend

Projects

None yet

Development

Successfully merging this pull request may close these issues.

BUG:Image flicker at top-left corner during slideshow/navigation transitions (valid images)

1 participant