Skip to content

Commit 6d3df23

Browse files
authored
Fix useSpring timing (#2027)
* Attempting fix * Latest * Latest * Updating * Latest * Updating test * Fixing tests
1 parent 66afc3e commit 6d3df23

File tree

3 files changed

+62
-7
lines changed

3 files changed

+62
-7
lines changed

dev/examples/useScroll.tsx

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,32 @@ export const Example = () => {
4444
const [isComplete, setIsComplete] = useState(false)
4545
const containerRef = useRef(null)
4646
const { scrollYProgress } = useElementScroll(containerRef)
47-
const yRange = useTransform(scrollYProgress, [0, 0.9], [0, 1])
48-
const pathLength = useSpring(yRange, { stiffness: 400, damping: 90 })
49-
50-
useEffect(() => yRange.on("change", (v) => setIsComplete(v >= 1)), [yRange])
47+
// const yRange = useTransform(scrollYProgress, [0, 0.9], [0, 1])
48+
// const pathLength = useSpring(yRange, {
49+
// stiffness: 100,
50+
// damping: 30,
51+
// restDelta: 0.001,
52+
// })
53+
54+
// const { scrollYProgress } = useScroll();
55+
const scaleX = useSpring(scrollYProgress, {
56+
stiffness: 100,
57+
damping: 30,
58+
restDelta: 0.001,
59+
restSpeed: 0.001,
60+
})
61+
62+
// return (
63+
// <>
64+
// <motion.div className="progress-bar" style={{ scaleX }} />
65+
// <h1>
66+
// <code>useScroll</code> with spring smoothing
67+
// </h1>
68+
// <LoremIpsum />
69+
// </>
70+
// );
71+
72+
// useEffect(() => yRange.on("change", (v) => setIsComplete(v >= 1)), [yRange])
5173

5274
return (
5375
<div
@@ -61,6 +83,17 @@ export const Example = () => {
6183
right: 0,
6284
}}
6385
>
86+
<motion.div
87+
style={{
88+
left: 0,
89+
position: "fixed",
90+
width: "100%",
91+
height: 10,
92+
background: "white",
93+
scaleX,
94+
transformOrigin: "0% 0%",
95+
}}
96+
/>
6497
<ContentPlaceholder />
6598
<svg className="progress-icon" viewBox="0 0 60 60">
6699
<motion.path
@@ -70,7 +103,7 @@ export const Example = () => {
70103
strokeDasharray="0px 10000px"
71104
d="M 0, 20 a 20, 20 0 1,0 40,0 a 20, 20 0 1,0 -40,0"
72105
style={{
73-
pathLength,
106+
// pathLength,
74107
rotate: 90,
75108
translateX: 5,
76109
translateY: 5,

packages/framer-motion/src/value/__tests__/use-spring.test.tsx

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ describe("useSpring", () => {
5555
})
5656

5757
test("creates a spring that animates to the subscribed motion value", async () => {
58-
const promise = new Promise((resolve) => {
58+
const promise = new Promise<number[]>((resolve) => {
5959
const output: number[] = []
6060
const Component = () => {
6161
const x = useMotionValue(0)
@@ -86,7 +86,15 @@ describe("useSpring", () => {
8686

8787
const resolved = await promise
8888

89-
expect(resolved).toEqual([0, 2, 4, 7, 10, 14, 19, 24, 29, 34])
89+
const testNear = (value: number, expected: number, deviation = 2) => {
90+
expect(
91+
value >= expected - deviation && value <= expected + deviation
92+
).toBe(true)
93+
}
94+
95+
testNear(resolved[0], 0)
96+
testNear(resolved[4], 8)
97+
testNear(resolved[8], 25)
9098
})
9199

92100
test("will not animate if immediate=true", async () => {

packages/framer-motion/src/value/use-spring.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { SpringOptions } from "../animation/types"
77
import { useIsomorphicLayoutEffect } from "../utils/use-isomorphic-effect"
88
import { AnimationPlaybackControls } from "../animation/types"
99
import { animateValue } from "../animation/animators/js"
10+
import { frameData } from "../frameloop/data"
11+
import { millisecondsToSeconds } from "../utils/time-conversion"
1012

1113
/**
1214
* Creates a `MotionValue` that, when `set`, will use a spring animation to animate to its new state.
@@ -61,6 +63,18 @@ export function useSpring(
6163
onUpdate: set,
6264
})
6365

66+
/**
67+
* If we're between frames, resync the animation to the frameloop.
68+
*/
69+
if (!frameData.isProcessing) {
70+
const delta = performance.now() - frameData.timestamp
71+
72+
if (delta < 30) {
73+
activeSpringAnimation.current.time =
74+
millisecondsToSeconds(delta)
75+
}
76+
}
77+
6478
return value.get()
6579
}, stopAnimation)
6680
}, [JSON.stringify(config)])

0 commit comments

Comments
 (0)