Skip to content

fix(core): clear pending native-scroll velocity timeout on destroy() - #542

Open
cipherprofessor wants to merge 1 commit into
darkroomengineering:mainfrom
cipherprofessor:fix/clear-reset-velocity-timeout-on-destroy
Open

cipherprofessor wants to merge 1 commit into
darkroomengineering:mainfrom
cipherprofessor:fix/clear-reset-velocity-timeout-on-destroy

Conversation

@cipherprofessor

Copy link
Copy Markdown

Fixes #541.

Problem

onNativeScroll schedules _resetVelocityTimeout (a 400ms setTimeout) whenever native-scroll velocity is nonzero, to reset velocity/isScrolling once the browser's native scroll settles. destroy() removed listeners and classes but never cleared that pending timeout.

If destroy() runs while that timeout is still pending, its callback fires anyway ~400ms later and sets isScrolling = false. That setter's private updateClassName() runs unconditionally on any change, and className's getter always starts from 'lenis' — so the base class gets re-added to rootElement well after the instance was destroyed and cleanUpClassName() already ran. Includes the common case of destroying an instance to honor prefers-reduced-motion, as described in the issue.

Fix

Clear _resetVelocityTimeout in destroy(), the same guard onNativeScroll already applies to itself at the top of its own body (clearing any previous pending timeout before scheduling a new one).

Testing

This repo has no test framework (no vitest/jest, no CI test step — confirmed by checking package.json scripts and searching for a test config). Verified with a standalone jsdom-based reproduction script exercising the actual, unmodified source end to end: minimal window/document polyfills (matchMedia, requestAnimationFrame, scrollTo/scrollY), autoResize disabled to avoid needing ResizeObserver, then the reporter's exact repro sequence — dispatch a native scroll, wait for the 'native' scrolling state to fire, destroy(), check the class immediately and again after 600ms.

Confirmed red against unmodified source (class returns after ~400ms, matching the reporter's exact numbers) and green after the fix (class stays cleared).

bun run build and bunx biome check are both clean.

onNativeScroll schedules _resetVelocityTimeout (a 400ms setTimeout)
whenever native-scroll velocity is nonzero, to reset velocity/isScrolling
once the browser's native scroll settles. destroy() removed listeners
and classes but never cleared that pending timeout.

If destroy() runs while that timeout is still pending, its callback
fires anyway ~400ms later and sets isScrolling = false. That setter's
private updateClassName() runs unconditionally on any change, and
className's getter always starts from 'lenis' -- so the base class gets
re-added to rootElement well after the instance was destroyed and
cleanUpClassName() already ran, including for the common case of
destroying an instance to honor prefers-reduced-motion.

Fix: clear _resetVelocityTimeout in destroy(), the same guard
onNativeScroll already applies to itself at the top of its own body
(clearing any previous pending timeout before scheduling a new one).

Testing: this repo has no test framework (no vitest/jest, no CI test
step -- confirmed by checking package.json scripts and searching for a
test config). Verified with a standalone jsdom-based reproduction
script exercising the actual, unmodified source end to end: minimal
window/document polyfills (matchMedia, requestAnimationFrame,
scrollTo/scrollY), autoResize disabled to avoid needing
ResizeObserver, then the reporter's exact repro sequence (dispatch a
native scroll, wait for the 'native' scrolling state to fire, destroy,
check the class immediately and again after 600ms). Confirmed red
against unmodified source (class returns after ~400ms) and green after
the fix (class stays cleared). `bun run build` and `bunx biome check`
are both clean.

Fixes darkroomengineering#541
@cipherprofessor
cipherprofessor requested a review from a team as a code owner September 21, 2026 08:04
@vercel

vercel Bot commented Sep 21, 2026

Copy link
Copy Markdown

@cipherprofessor is attempting to deploy a commit to the Darkroom Team on Vercel.

A member of the Team first needs to authorize it.

@darkestdarky-bot darkestdarky-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The diff adds a null-guarded clearTimeout for _resetVelocityTimeout in destroy(). This prevents a pending native-scroll velocity reset from firing after teardown and re-adding the root class. The change is minimal, confined to packages/core/src/lenis.ts, and matches the existing guard pattern; no correctness, scope, or standards issues are visible in the supplied hunk.

Review coverage: 16/16 diff lines supplied. Inline comments are limited to fully visible, valid right-side hunks. Reviewed commit: 2e14ce85aa482d869799df4a25a872bb74400676.

@arzafran arzafran closed this Oct 1, 2026
@arzafran arzafran reopened this Oct 1, 2026

This branch has not been deployed

No deployments
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.

destroy() can restore the lenis class after a native scroll timeout

2 participants