[BUG] Window scroll is compensated twice during drag, making Reorder auto-scroll run away
I maintainer di solito rispondono entro 1 giorno
Valutazione
- Difficoltà
- 3/5
- Tempo stimato
- 1-2 giorni
- Idoneità per principianti
- 78/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- cypress, react, typescript
Direzione di ricerca
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.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
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
pointermovere-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
- Open the sandbox in a full window.
- Drag "Item 3" down to the bottom edge of the window (stay inside the window).
- Hold still for a second, then wiggle slightly while staying at the edge.
- The readout shows calls above 25px. On wiggle the page jumps to the end, and single
scrollBycalls 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.
- Lingua principale
- TypeScript
- Stelle
- 34.4k
- Fork
- 1.4k
- Merge medio
- 23h 37m
- PR unite (30g)
- 96
Preparare l'ambiente
Avvia il container di sviluppo del progetto nel browser, con il tuo account GitHub.
- Nessun Dockerfile né file Docker Compose
- Nessun modello di pull request
- Leggi la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di motiondivision/motion
-
[BUG] Keyboard press leaves a listener on every focus and on every Enter press (duplicated pointerup)Forse già presa Una pull request collegata a questa issue è aperta o già unita. Aperta
Difficoltà 2/5 1-3 ore Idoneità per principianti 74/100
motiondivision/motion#3935 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
[BUG] animate change made while inside a hidden <Activity> is ignored on revealForse già presa @mattgperry l’ha presa oggi. Aperta
Difficoltà 3/5 1-2 giorni Idoneità per principianti 45/100
motiondivision/motion#3942 ·
I maintainer di solito rispondono entro 1 giorno
-
[FEATURE] AnimateSuspense for transitions between suspense fallbacks and streamed contentForse di nuovo libera Una pull request per questa issue è stata chiusa senza essere unita. Apertafeature
Difficoltà 5/5 Più di una settimana Idoneità per principianti 35/100
motiondivision/motion#3173 · 9 reazioni ·
I maintainer di solito rispondono entro 1 giorno
-
bug
Difficoltà 4/5 3-5 giorni Idoneità per principianti 35/100
motiondivision/motion#2662 · 2 commenti · 4 reazioni ·
I maintainer di solito rispondono entro 1 giorno
-
feature
Difficoltà 4/5 3-5 giorni Idoneità per principianti 35/100
motiondivision/motion#2658 · 1 reazione ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di motiondivision/motion
Issue simili
-
area:workflow bug ready-for-human
Difficoltà 1/5 Meno di un'ora Idoneità per principianti 78/100
fil-donadoni/tolaria#6108 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
apache/rocketmq-dashboard#7234 ·
I maintainer di solito rispondono entro 3 giorni
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
vitest-dev/vitest#11656 ·
I maintainer di solito rispondono entro 1 giorno
-
e2e-failure ready-to-code
Difficoltà 2/5 1-3 ore Idoneità per principianti 68/100
redhat-developer/rhdh-plugin-export-overlays#4460 · 1 commento ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
ehmpathy/rhachet-roles-bhrain#655 ·
I maintainer di solito rispondono entro 1 giorno