Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding animation events to motion value #1826

Merged
merged 3 commits into from Dec 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.md
Expand Up @@ -4,6 +4,25 @@ Framer Motion adheres to [Semantic Versioning](http://semver.org/).

Undocumented APIs should be considered internal and may change without warning.

## [7.10.0] 2022-12-15

### Added

- `.on()` event method to `MotionValue`.
- `"animationStart"`, `"animationComplete"`, `"animationCancel"` and `"change"` events for `MotionValue`.

## [7.9.0] 2022-12-14

### Added

- Hardware-accelerated `opacity` animations.

## [7.8.1] 2022-12-14

### Changed

- Refactored animation pipeline to better accomodate WAAPI.

## [7.9.1] 2022-12-14

### Fixed
Expand Down
4 changes: 2 additions & 2 deletions dev/examples/Drag-external-handlers.tsx
Expand Up @@ -44,7 +44,7 @@ export const App = () => {
)

useEffect(() => {
return transform.onChange(v => console.log(v))
return transform.on("change", (v) => console.log(v))
})

return (
Expand All @@ -54,7 +54,7 @@ export const App = () => {
_dragX={x}
_dragY={y}
dragConstraints={ref}
onMeasureDragConstraints={constraints => constraints}
onMeasureDragConstraints={(constraints) => constraints}
style={{
backgroundColor: color,
...child,
Expand Down
2 changes: 1 addition & 1 deletion dev/examples/ThreeSwitch.tsx
Expand Up @@ -114,7 +114,7 @@ export function useAnimatedText(target, textTransition) {
useEffect(() => {
ref.current.innerText = target.toFixed(2)

return value.onChange((v) => {
return value.on("change", (v) => {
ref.current.innerText = v.toFixed(2)
})
})
Expand Down
2 changes: 1 addition & 1 deletion dev/examples/useScroll.tsx
Expand Up @@ -47,7 +47,7 @@ export const Example = () => {
const yRange = useTransform(scrollYProgress, [0, 0.9], [0, 1])
const pathLength = useSpring(yRange, { stiffness: 400, damping: 90 })

useEffect(() => yRange.onChange((v) => setIsComplete(v >= 1)), [yRange])
useEffect(() => yRange.on("change", (v) => setIsComplete(v >= 1)), [yRange])

return (
<div
Expand Down
2 changes: 1 addition & 1 deletion dev/examples/useViewportScroll.tsx
Expand Up @@ -46,7 +46,7 @@ export const Example = () => {
const yRange = useTransform(scrollYProgress, [0, 0.9], [0, 1])
const pathLength = useSpring(yRange, { stiffness: 400, damping: 90 })

useEffect(() => yRange.onChange((v) => setIsComplete(v >= 1)), [yRange])
useEffect(() => yRange.on("change", (v) => setIsComplete(v >= 1)), [yRange])

return (
<>
Expand Down
2 changes: 1 addition & 1 deletion dev/tests/drag-to-reorder.tsx
Expand Up @@ -13,7 +13,7 @@ const Item = ({ item, axis }) => {

useEffect(() => {
let isActive = false
axisValue.onChange((latestY) => {
axisValue.on("change", (latestY) => {
const wasActive = isActive
if (latestY !== 0) {
isActive = true
Expand Down
Expand Up @@ -385,7 +385,7 @@ describe("animate prop as object", () => {
test("respects repeatDelay prop", async () => {
const promise = new Promise<number>((resolve) => {
const x = motionValue(0)
x.onChange(() => {
x.on("change", () => {
setTimeout(() => resolve(x.get()), 50)
})
const Component = () => (
Expand Down
4 changes: 2 additions & 2 deletions packages/framer-motion/src/motion/__tests__/variant.test.tsx
Expand Up @@ -511,7 +511,7 @@ describe("animate prop as variant", () => {

React.useEffect(
() =>
a.onChange((latest) => {
a.on("change", (latest) => {
if (latest >= 1 && b.get() === 0) resolve(true)
}),
[a, b]
Expand Down Expand Up @@ -564,7 +564,7 @@ describe("animate prop as variant", () => {

React.useEffect(
() =>
a.onChange((latest) => {
a.on("change", (latest) => {
if (latest >= 1 && b.get() === 0) resolve(true)
}),
[a, b]
Expand Down
8 changes: 6 additions & 2 deletions packages/framer-motion/src/render/VisualElement.ts
Expand Up @@ -412,7 +412,8 @@ export abstract class VisualElement<
private bindToMotionValue(key: string, value: MotionValue) {
const valueIsTransform = transformProps.has(key)

const removeOnChange = value.onChange(
const removeOnChange = value.on(
"change",
(latestValue: string | number) => {
this.latestValues[key] = latestValue
this.props.onUpdate &&
Expand All @@ -424,7 +425,10 @@ export abstract class VisualElement<
}
)

const removeOnRenderRequest = value.onRenderRequest(this.scheduleRender)
const removeOnRenderRequest = value.on(
"renderRequest",
this.scheduleRender
)

this.valueSubscriptions.set(key, () => {
removeOnChange()
Expand Down
73 changes: 73 additions & 0 deletions packages/framer-motion/src/value/__tests__/index.test.ts
@@ -0,0 +1,73 @@
import { motionValue } from "../"
import { animate } from "../../animation/animate"

describe("motionValue", () => {
test("change event fires when value changes", () => {
const value = motionValue(0)
const callback = jest.fn()

value.on("change", callback)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we keeping the old API around?


expect(callback).not.toBeCalled()
value.set(1)
expect(callback).toBeCalledTimes(1)
value.set(1)
expect(callback).toBeCalledTimes(1)
})

test("renderRequest event fires", () => {
const value = motionValue(0)
const callback = jest.fn()

value.on("renderRequest", callback)

expect(callback).not.toBeCalled()
value.set(1)
expect(callback).toBeCalledTimes(1)
})

test("animationStart event fires", () => {
const value = motionValue(0)
const callback = jest.fn()

value.on("animationStart", callback)

expect(callback).not.toBeCalled()

animate(value, 2)

expect(callback).toBeCalledTimes(1)
})

test("animationCancel event fires", () => {
const value = motionValue(0)
const callback = jest.fn()

value.on("animationCancel", callback)

expect(callback).not.toBeCalled()

animate(value, 1)
animate(value, 2)

expect(callback).toBeCalledTimes(1)
})

test("animationComplete event fires", async () => {
const value = motionValue(0)
const callback = jest.fn()

value.on("animationComplete", callback)

expect(callback).not.toBeCalled()

animate(value, 1, { duration: 0.01 })

return new Promise<void>((resolve) => {
setTimeout(() => {
expect(callback).toBeCalledTimes(1)
resolve()
}, 100)
})
})
})
Expand Up @@ -52,8 +52,8 @@ describe("useMotionValue", () => {
const onRenderRequest = jest.fn()
const Component = () => {
const x = useMotionValue(100)
x.onChange(onChange)
x.onRenderRequest(onRenderRequest)
x.on("change", onChange)
x.on("renderRequest", onRenderRequest)

x.set(500)

Expand Down
Expand Up @@ -13,7 +13,7 @@ describe("useSpring", () => {
const x = useSpring(0)

React.useEffect(() => {
x.onChange((v) => resolve(v))
x.on("change", (v) => resolve(v))
x.set(100)
})

Expand All @@ -37,7 +37,7 @@ describe("useSpring", () => {
const y = useSpring(x)

React.useEffect(() => {
y.onChange((v) => resolve(v))
y.on("change", (v) => resolve(v))
x.set(100)
})

Expand All @@ -64,7 +64,7 @@ describe("useSpring", () => {
} as any)

React.useEffect(() => {
return y.onChange((v) => {
return y.on("change", (v) => {
if (output.length >= 10) {
resolve(output)
} else {
Expand Down Expand Up @@ -105,6 +105,7 @@ describe("useSpring", () => {
rerender(<Component target={a} />)
rerender(<Component target={a} />)

expect(((a! as any).updateSubscribers! as any).getSize()).toBe(1)
// Cast to any here as `.events` is private API
expect((a as any).events.change.getSize()).toBe(1)
})
})
Expand Up @@ -46,13 +46,13 @@ describe("useVelocity", () => {

React.useEffect(() => {
const unsubscribe = pipe(
x.onChange((v) => {
x.on("change", (v) => {
output.push(Math.round(v))
}),
xVelocity.onChange((v) => {
xVelocity.on("change", (v) => {
outputVelocity.push(Math.round(v))
}),
xAcceleration.onChange((v) => {
xAcceleration.on("change", (v) => {
outputAcceleration.push(Math.round(v))
})
)
Expand Down