Skip to content

Commit

Permalink
Merge pull request #2670 from framer/fix/sample-on-stop
Browse files Browse the repository at this point in the history
Batching calls to `useSpring`
  • Loading branch information
mergetron[bot] authored May 16, 2024
2 parents d4826ad + 6dcdc93 commit e674e58
Show file tree
Hide file tree
Showing 2 changed files with 64 additions and 48 deletions.
64 changes: 35 additions & 29 deletions dev/examples/useSpring.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,35 +4,18 @@ import { useEffect, useRef, useState } from "react"
const spring = {
stiffness: 300,
damping: 28,
restDelta: 0.001,
restSpeed: 0.001,
}

const useMousePosition = () => {
const [mousePosition, setMousePosition] = useState({ x: null, y: null })

const updateMousePosition = (e) => {
setMousePosition({ x: e.clientX, y: e.clientY })
}

useEffect(() => {
window.addEventListener("mousemove", updateMousePosition)

return () =>
window.removeEventListener("mousemove", updateMousePosition)
}, [])

return mousePosition
restDelta: 0.00001,
restSpeed: 0.00001,
}

function DragExample() {
const dragX = useMotionValue(0)
const dragY = useMotionValue(0)
const x = useSpring(dragX)
const x = useSpring(dragX, spring)
const y = useSpring(dragY, spring)
return (
<motion.div
drag
drag="x"
dragMomentum={false}
_dragX={dragX}
_dragY={dragY}
Expand All @@ -44,14 +27,19 @@ function DragExample() {
}

function RerenderExample() {
const { x, y } = useMousePosition()
const [{ x, y }, setMousePosition] = useState({ x: null, y: null })

const updateMousePosition = useRef((e) => {
setMousePosition({ x: e.clientX, y: e.clientY })
})

const size = 40
const ref = useRef<HTMLDivElement>(null)
console.log(x)

return (
<motion.div
ref={ref}
animate={{ x }}
animate={{ x, y }}
transition={spring}
style={{
width: 100,
Expand All @@ -60,6 +48,24 @@ function RerenderExample() {
position: "absolute",
inset: 0,
}}
onTapStart={() => {
window.addEventListener(
"mousemove",
updateMousePosition.current
)
}}
onTap={() => {
window.removeEventListener(
"mousemove",
updateMousePosition.current
)
}}
onTapCancel={() => {
window.removeEventListener(
"mousemove",
updateMousePosition.current
)
}}
>
Rerender
</motion.div>
Expand All @@ -69,15 +75,15 @@ function RerenderExample() {
function MouseEventExample() {
const xPoint = useMotionValue(0)
const yPoint = useMotionValue(0)
const x = useSpring(xPoint, spring)
const y = useSpring(yPoint, spring)
const x = useSpring(0, spring)
const y = useSpring(0, spring)
const ref = useRef<HTMLDivElement>(null)
const onMove = useRef<(event: MouseEvent) => void>(
({ clientX, clientY }: MouseEvent) => {
const element = ref.current!

xPoint.set(clientX - element.offsetLeft - element.offsetWidth / 2)
yPoint.set(clientY - element.offsetTop - element.offsetHeight / 2)
x.set(clientX - element.offsetLeft - element.offsetWidth / 2)
y.set(clientY - element.offsetTop - element.offsetHeight / 2)
}
)

Expand All @@ -92,7 +98,7 @@ function MouseEventExample() {
return (
<motion.div
ref={ref}
style={{ width: 100, height: 100, background: "yellow", x, y }}
style={{ width: 100, height: 100, background: "yellow", x }}
onTapStart={startPointer}
onTapCancel={cancelPointer}
onTap={cancelPointer}
Expand Down
48 changes: 29 additions & 19 deletions packages/framer-motion/src/value/use-spring.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { useMotionValue } from "./use-motion-value"
import { MotionConfigContext } from "../context/MotionConfigContext"
import { SpringOptions } from "../animation/types"
import { useIsomorphicLayoutEffect } from "../utils/use-isomorphic-effect"
import { frameData } from "../frameloop"
import { frame, frameData } from "../frameloop"
import {
MainThreadAnimation,
animateValue,
Expand Down Expand Up @@ -39,6 +39,31 @@ export function useSpring(
null
)
const value = useMotionValue(isMotionValue(source) ? source.get() : source)
const latestValue = useRef<number>(value.get())
const latestSetter = useRef<(v: number) => void>(() => {})

const startAnimation = () => {
/**
* If the previous animation hasn't had the chance to even render a frame, render it now.
*/
const animation = activeSpringAnimation.current

if (animation && animation.time === 0) {
animation.sample(frameData.delta)
}

stopAnimation()

activeSpringAnimation.current = animateValue({
keyframes: [value.get(), latestValue.current],
velocity: value.getVelocity(),
type: "spring",
restDelta: 0.001,
restSpeed: 0.01,
...config,
onUpdate: latestSetter.current,
})
}

const stopAnimation = () => {
if (activeSpringAnimation.current) {
Expand All @@ -54,25 +79,10 @@ export function useSpring(
*/
if (isStatic) return set(v)

/**
* If the previous animation hasn't had the chance to even render a frame, render it now.
*/
const animation = activeSpringAnimation.current
if (animation && animation.time === 0) {
animation.sample(frameData.delta)
}

stopAnimation()
latestValue.current = v
latestSetter.current = set

activeSpringAnimation.current = animateValue({
keyframes: [value.get(), v],
velocity: value.getVelocity(),
type: "spring",
restDelta: 0.001,
restSpeed: 0.01,
...config,
onUpdate: set,
})
frame.update(startAnimation)

return value.get()
}, stopAnimation)
Expand Down

0 comments on commit e674e58

Please sign in to comment.