Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

[BUG] Window scroll is compensated twice during drag, making Reorder auto-scroll run away

Open
#3,944 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@mattgperry is already working on this.

Since Oct 19, 2026.

  • #3951 by @mattgperry — open

Assessment

Difficulty
3/5
Estimated time
1-2 days
Newbie friendliness
78/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
cypress, react, typescript
Domain
frontend, testing

Research direction

Start with PanSession.handlePointerMove and handleScroll, then read the existing window-scroll drag case in drag-scroll-while-drag.ts. Reproduce the reported event ordering: dispatch a pointermove whose pageY includes the new window scroll before its scroll event fires. Add a regression assertion that the dragged element does not move by the extra scroll delta, and confirm auto-scroll stays within maxSpeed for a pointer inside the viewport.

Written by the indexing model from the issue text.

Description

1. Read the FAQs 👇

Done.

2. Describe the bug

While dragging, PanSession.handleScroll adds the window scroll delta to lastMoveEventInfo.point, assuming the last pointer point was captured before that scroll. But if a pointermove is handled after the window has scrolled and before the matching scroll event fires, its pageY already includes the new scroll. handleScroll then adds the same delta a second time, so the drag point ends up below the real pointer by one frame's scroll amount.

With Reorder on a page that scrolls the document (no overflow: auto container, supported since #3508), this feeds back into itself:

  • Auto-scroll reads the drifted point, decides the pointer is past the edge, and scrolls more than maxSpeed (25px). The intensity is not capped at 1 when the pointer looks like it is beyond the edge.
  • The next pointermove re-captures the real point, and the following scroll event adds that larger delta again.
  • The drift grows every round. In one Safari drag it went 95 → 156 → 347 → 1,426px over four pointermoves, and the next call scrolled 21,231px.

In practice the list jumps straight to the top or bottom while you drag. Sometimes the dragged item flies off far enough that the document gets taller.

Instrumented log on 12.43.0 (Chromium), with the pointer held still 2px above the bottom edge of an 800px viewport:

pointermove   clientY 798, pageY 858 (= 798 + scrollY 60)   ← correct
handleScroll  delta 15 → point 858 + 15 = 873               ← counted twice, 15px too low
autoScroll    viewport point 813 (real 798) → amount 39.7  ← above maxSpeed 25
handleScroll  delta 39.5 → drift stays 15 …

The relevant code is unchanged on main (v14.3.0), and the repro below fails on 14.3.0.

3. CodeSandbox reproduction

https://codesandbox.io/s/4lw8kg (preview in a full window: https://4lw8kg.csb.app/)

It's a page-scrolling Reorder.Group with 40 items. It wraps window.scrollBy and counts auto-scroll calls above 25px while the pointer is inside the viewport, which the auto-scroll formula should never produce.

4. Steps to reproduce

  1. Open the sandbox in a full window.
  2. Drag "Item 3" down to the bottom edge of the window (stay inside the window).
  3. Hold still for a second, then wiggle slightly while staying at the edge.
  4. The readout shows calls above 25px. On wiggle the page jumps to the end, and single scrollBy calls can reach thousands of px.

5. Expected behavior

The drag point should match the pointer, so a single auto-scroll call stays at or below 25px while the pointer is inside the viewport.

6. Video or screenshots

Measured with the repro on framer-motion 14.3.0 (Playwright, 1280×800, pointer 2px above the bottom edge):

Hold still Wiggle at edge
Chromium 38/47 calls > 25px, max 28.1px max 14,151,139px; document grew from 5,668 to 43,372px
WebKit 37/45 calls > 25px, max 27.0px max 7,293px, jumped to the end

On 12.43.0 with a real mouse or touch, the largest single call was 39.7px in Firefox 155, 1,714,438px in Safari 26.6.2 (macOS), and 17,073px with touch on iOS 18.7 (WKWebView). Chrome 155 with a real mouse showed up to 14.5px of drift.

7. Environment details

  • framer-motion 14.3.0 and 12.43.0, React 19
  • macOS: Chrome 155, Firefox 155, Safari 26.6.2; iOS 18.7 (WKWebView, touch)

Suggested fix

pageX/pageY from a pointer event already include the current window scroll, so reset the window baseline whenever a new pointer point is taken:

 private handlePointerMove = (event: PointerEvent, info: EventInfo) => {
+    if (this.scrollPositions.has(window)) {
+        this.scrollPositions.set(window, { x: window.scrollX, y: window.scrollY })
+    }
     this.lastMoveEvent = event

Element scroll is unaffected: element scroll doesn't change pageX/pageY, and that branch adjusts the history origin instead.

Results with this change applied to 14.3.0 in the same repro:

Hold still Wiggle at edge
Chromium 0/46 calls > 25px, max 23.0px 0/46, max 23.0px
WebKit 0/45 calls > 25px, max 23.0px 0/40, max 23.0px

23.0px is the expected 25 × (1 − 2/50)² for a pointer 2px from the edge, so the drift is gone. With the pointer held completely still, handleScroll still keeps the dragged item under the pointer while the page auto-scrolls. I checked this on 12.43.0: the offset stayed constant while scrollY went from 98 to 640.

A Cypress case could go next to "Drag with window scroll during drag" in drag-scroll-while-drag.ts: scroll the window, then dispatch a pointermove whose pageY already includes that scroll before the scroll event fires, and assert that the element doesn't move by the extra delta. I haven't written it.

Separately, getScrollAmount doesn't cap intensity at 1, so a pointer that really is beyond the edge (for example, a mouse dragged below the window) also scrolls faster than maxSpeed. Capping it would also bound the damage from any future drift.

Dominant language
TypeScript
Stars
34.4k
Forks
1.4k
Avg merge
23h 37m
Merged PRs (30d)
96

Getting set up

Open in Codespaces

Starts the project's dev container in your browser, under your own GitHub account.

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from motiondivision/motion

All issues in motiondivision/motion

Similar issues

More TypeScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.