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

[BUG] Reorder auto-scroll speed scales with the display's refresh rate

Closed Beginner friendly
#3,955 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
82/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
react, typescript

Research direction

Start with getScrollAmount in packages/framer-motion/src/components/Reorder/utils/auto-scroll.ts and trace how Reorder.Item calls autoScrollIfNeeded during dragging. Check how frameData.delta is exposed from motion-dom and how existing tests cover auto-scroll. Done means auto-scroll travels at a consistent speed across frame rates while retaining the expected speed at 60fps.

Written by the indexing model from the issue text.

Description

1. Read the FAQs 👇

Done.

2. Describe the bug

Reorder auto-scroll moves a fixed number of pixels per frame, so how fast the page scrolls depends on how many frames the browser runs per second. On a 120Hz display it scrolls twice as fast as on a 60Hz display (measured below), and a 240Hz display would be four times as fast.

getScrollAmount in packages/framer-motion/src/components/Reorder/utils/auto-scroll.ts returns maxSpeed * intensity², with maxSpeed = 25. Reorder.Item calls autoScrollIfNeeded from onDrag, which runs every frame while the item is held near an edge, and each call scrolls by that amount. Nothing scales it by elapsed time (frameData.delta).

This is unchanged on main after #3951.

3. CodeSandbox reproduction

https://codesandbox.io/s/vxj4px (full window: https://vxj4px.csb.app/)

A page-scrolling Reorder.Group with 200 items on framer-motion 14.4.0. A panel shows Motion's frames per second and how far auto-scroll moved the page in the last second.

?fps=30 throttles requestAnimationFrame to 30fps, so the difference shows up without a high refresh rate display. It's installed before framer-motion is imported, because the frameloop captures requestAnimationFrame on load.

4. Steps to reproduce

  1. Open the full window preview.
  2. Drag "Item 3" to about 10px above the bottom edge and hold still.
  3. Note "scrolled in last 1s".
  4. Repeat with ?fps=30 added to the URL, or on a display with a different refresh rate.

5. Expected behavior

The same scroll speed in pixels per second regardless of refresh rate, like Motion's time-based animations.

6. Video or screenshots

Measured with Playwright on 14.4.0, pointer held 10px above the bottom edge of an 800px viewport, over 2 seconds. Headed Chromium on a 120Hz ProMotion display; the 60fps and 30fps rows use ?fps=:

Display frames Auto-scroll calls Page scrolled Per call (avg)
120fps (native) 240 315 4,530px 14.4px
?fps=60 120 158 2,268px 14.4px
?fps=30 60 71 976px 13.7px

Headless WebKit at its native 60fps vs ?fps=30: 2,095px vs 1,029px.

The amount per call stays the same, so the speed follows the frame rate: 120Hz scrolls twice as fast as 60Hz.

Calls outnumber display frames by 20–30% at every rate. That's the projection system's synchronous flush after React commits a layout change, here each reorder (create-projection-node.ts, "Manually flush any pending updates"), which runs the update step, and so PanSession.updatePoint → onDrag → autoScrollIfNeeded, a second time in the same display frame. That flush sets frameData.delta to the real elapsed time, a few ms, but the per-frame amount scrolls the full distance again. Scaling by frameData.delta handles that case as well.

Suggested fix

Treat maxSpeed as pixels per second and scale by the frame delta. 1500px/s keeps today's speed at 60Hz:

+import { frameData } from "motion-dom"
+
 const threshold = 50
-const maxSpeed = 25
+// Pixels per second (25px per frame at 60fps)
+const maxSpeed = 1500

 function scrollSpeed(distanceFromEdge: number): number {
     const intensity = Math.min(1, 1 - distanceFromEdge / threshold)
-    return maxSpeed * intensity * intensity
+    return maxSpeed * intensity * intensity * (frameData.delta / 1000)
 }

frameData.delta is already clamped to 40ms, so a long frame scrolls at most 60px.

For reference, Atlassian's pragmatic-drag-and-drop made the same call in its auto-scroll package (configuration.ts): "Using 'per second' rather than 'per frame' as we want a consistent scroll speed regardless of frame rate", with its fast preset defined as 25 * 60 px/s.

With this applied to 14.4.0 in the same setup, the distance in 2 seconds no longer depends on the frame rate: 1,898px at 120fps, 1,872px at 60fps and 1,732px at 30fps (Chromium), 1,795px vs 1,733px at 60 and 30fps (WebKit).

7. Environment details

  • framer-motion 14.4.0, React 19.2.0
  • macOS 26.6.2, MacBook Pro (120Hz ProMotion)
  • Playwright Chromium 156 (headed) and WebKit 27.2 (headless)
Dominant language
TypeScript
Stars
34.4k
Forks
1.4k
Avg merge
23h 3m
Merged PRs (30d)
108

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.