Skip to content

fix: reduce scroll animation jank on Android#37

Merged
bfintal merged 2 commits into
developfrom
fix/android-scroll-jank
Jul 13, 2026
Merged

fix: reduce scroll animation jank on Android#37
bfintal merged 2 commits into
developfrom
fix/android-scroll-jank

Conversation

@bfintal

@bfintal bfintal commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes choppy / late-loading scroll-triggered animations on Android Chrome (reported for the WPI website — animations that appear promptly on iOS Safari "pop in" late or stutter on Android). Ref: gambitph/WP-Interactions#13.

Root causes and fixes:

  • enterViewport — Android Chrome batches/defers IntersectionObserver callbacks during momentum (fling) scrolling, so reveals fire noticeably late vs iOS Safari. Added a bottom rootMargin lead time (0px 0px 15% 0px) so the observer triggers slightly before the element scrolls in. Also guarded against a zero / not-yet-laid-out element height, which previously made the normalized threshold NaN and could throw when constructing the observer.
  • pageScrolling / elementScrolling — scroll listeners were non-passive (blocking scroll on mobile) and ran anime.js on every single scroll event. Switched to { passive: true } and coalesced work into a single requestAnimationFrame per frame. Also guarded divide-by-zero when the scroll range is 0.

Test plan

  • Desktop Chrome/Firefox/Safari: enter-viewport reveals still trigger at the configured threshold.
  • Android Chrome: fast-scroll a page with enter-viewport reveals — animations appear on time, no late pop-in.
  • Android Chrome: page-scroll and element-scroll (scrubbed) animations track smoothly without scroll jank.
  • iOS Safari: no regression.
  • Element with a lazy-loaded image above it still initializes without console errors.

Made with Cursor

Summary by CodeRabbit

  • Bug Fixes
    • Improved scrolling interactions for smoother, more responsive animation updates.
    • Prevented excessive animation updates during rapid scrolling.
    • Improved viewport-entry triggers, including earlier activation and more reliable behavior for varying element sizes.
    • Ensured scrolling interactions clean up correctly when no longer active.

Android Chrome batches/defers IntersectionObserver callbacks during
momentum scrolling and blocks scroll on non-passive listeners, causing
scroll-triggered animations to appear choppy and load late compared to
iOS Safari.

- enterViewport: add a bottom rootMargin lead time so reveals fire
  before the throttled callbacks lag, and guard against a zero/not-yet
  laid-out element height that could make the threshold NaN and throw.
- pageScrolling/elementScrolling: use passive scroll listeners and
  coalesce work into a single requestAnimationFrame per frame instead
  of running anime.js on every scroll event.

Co-authored-by: Cursor <cursoragent@cursor.com>
@bfintal bfintal requested a review from Arukuen July 10, 2026 12:04
@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@coderabbitai

coderabbitai Bot commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@bfintal, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 50 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cb0563cb-0b2a-4dee-9ada-ea0dc1c36730

📥 Commits

Reviewing files that changed from the base of the PR and between 19d6330 and a457aba.

📒 Files selected for processing (2)
  • src/interaction-types/frontend/enterViewport.js
  • src/interaction-types/frontend/pageScrolling.js
📝 Walkthrough

Walkthrough

Scrolling interactions now coalesce animation seeks per animation frame with passive listeners and cleanup cancellation. Viewport-entry interactions guard threshold calculation against invalid dimensions and trigger observation earlier using a bottom root margin.

Changes

Frontend interaction updates

Layer / File(s) Summary
Scroll animation scheduling
src/interaction-types/frontend/elementScrolling.js, src/interaction-types/frontend/pageScrolling.js
Scroll-driven animation updates use initial update helpers, passive listeners, rAF coalescing, and pending-frame cancellation during teardown.
Viewport threshold configuration
src/interaction-types/frontend/enterViewport.js
IntersectionObserver thresholds are clamped safely when element height is unavailable or non-positive, and observation uses a 15% bottom root margin.

Estimated code review effort: 3 (Moderate) | ~20 minutes

🚥 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 and concisely summarizes the main change: reducing Android scroll animation jank.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/android-scroll-jank

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.

github-actions Bot added a commit that referenced this pull request Jul 10, 2026
@github-actions

github-actions Bot commented Jul 10, 2026

Copy link
Copy Markdown

🤖 Pull request artifacts

file commit
pr37-interactions-37-merge.zip a457aba

@bfintal bfintal self-assigned this Jul 10, 2026

@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

🧹 Nitpick comments (1)
src/interaction-types/frontend/pageScrolling.js (1)

17-42: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting the shared rAF coalescing pattern.

The scrollHandler + rAF coalescing + passive listener + cleanup logic is now duplicated verbatim between elementScrolling.js and pageScrolling.js. A shared helper (e.g., createRafThrottledScrollListener(update)) would eliminate the duplication and ensure both files stay in sync — the missing clamp above is a direct consequence of this divergence.

🤖 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 `@src/interaction-types/frontend/pageScrolling.js` around lines 17 - 42,
Extract the duplicated scroll-event rAF coalescing, passive listener
registration, and cancellation cleanup from pageScrolling.js and
elementScrolling.js into a shared helper such as
createRafThrottledScrollListener(update). Update both callers to use the helper
while preserving immediate update behavior, animation destruction, and listener
cleanup, ensuring shared logic remains synchronized.
🤖 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 `@src/interaction-types/frontend/pageScrolling.js`:
- Around line 10-15: Clamp the calculated scrolled value to the [0, 1] range
before passing it to animation.seekPercentage in the update function, matching
the behavior of elementScrolling.js and preserving the existing first-call
smoothness handling.

---

Nitpick comments:
In `@src/interaction-types/frontend/pageScrolling.js`:
- Around line 17-42: Extract the duplicated scroll-event rAF coalescing, passive
listener registration, and cancellation cleanup from pageScrolling.js and
elementScrolling.js into a shared helper such as
createRafThrottledScrollListener(update). Update both callers to use the helper
while preserving immediate update behavior, animation destruction, and listener
cleanup, ensuring shared logic remains synchronized.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: d6506bc6-82b9-4083-8454-0d910451a705

📥 Commits

Reviewing files that changed from the base of the PR and between 29fdf72 and 19d6330.

📒 Files selected for processing (3)
  • src/interaction-types/frontend/elementScrolling.js
  • src/interaction-types/frontend/enterViewport.js
  • src/interaction-types/frontend/pageScrolling.js

Comment thread src/interaction-types/frontend/pageScrolling.js
Clamp page-scrolling progress to [0, 1] to match elementScrolling and
avoid invalid seeks during iOS overscroll. Apply the enter-viewport
rootMargin lead time only on Android so desktop and iOS keep the
previous intersection behavior.

Co-authored-by: Cursor <cursoragent@cursor.com>
@cursor

cursor Bot commented Jul 10, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

github-actions Bot added a commit that referenced this pull request Jul 10, 2026
@bfintal bfintal merged commit b738046 into develop Jul 13, 2026
1 check passed
@bfintal bfintal deleted the fix/android-scroll-jank branch July 13, 2026 09:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants