Skip to content

Commit

Permalink
Fixing unit conversion batching (#2562)
Browse files Browse the repository at this point in the history
* Adding breaking test

* Batching

* Fixing unit conversion
  • Loading branch information
mattgperry authored Mar 19, 2024
1 parent e9cdc63 commit 7a3a9cd
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 40 deletions.
33 changes: 33 additions & 0 deletions dev/tests/animate-x-percent.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { motion, AnimatePresence } from "framer-motion"
import * as React from "react"

export const App = () => {
const output = React.useRef<Array<string | number>>([])
const ref = React.useRef<HTMLDivElement>(null)
const [state, setState] = React.useState(true)

return (
<div style={{ height: 100, width: 200, display: "flex" }}>
<AnimatePresence>
{state ? (
<motion.div
id="test"
ref={ref}
animate={{ x: "100%", y: "100%", rotate: "-30deg" }}
style={{ width: 200, background: "red" }}
onClick={() => setState(false)}
transition={{ duration: 2 }}
onUpdate={({ x }) => {
output.current.push(x)
}}
onAnimationComplete={() => {
if (output.current[0] === "100%") {
ref.current!.innerHTML = "Error"
}
}}
/>
) : null}
</AnimatePresence>
</div>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,13 @@ describe("Unit conversion", () => {
expect($element.innerText).not.to.equal("Error")
})
})

it("animates translation from px to percent", () => {
cy.visit("?test=animate-x-percent")
.wait(200)
.get("#test")
.should(([$element]: any) => {
expect($element.innerText).not.to.equal("Error")
})
})
})
31 changes: 8 additions & 23 deletions packages/framer-motion/src/render/dom/DOMKeyframesResolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
isNumOrPxType,
positionalKeys,
positionalValues,
removeNonTranslationalTransform,
} from "./utils/unit-conversion"
import { findDimensionValueType } from "./value-types/dimensions"
import {
Expand All @@ -21,11 +20,10 @@ export class DOMKeyframesResolver<
T extends string | number
> extends KeyframeResolver<T> {
name: string
protected element: VisualElement<HTMLElement | SVGElement>
element: VisualElement<HTMLElement | SVGElement>

private removedTransforms?: [string, string | number][]
private measuredOrigin?: string | number
private suspendedScrollY?: number

constructor(
unresolvedKeyframes: UnresolvedKeyframes<string | number>,
Expand Down Expand Up @@ -121,19 +119,6 @@ export class DOMKeyframesResolver<
}
}

unsetTransforms() {
const { element, name, unresolvedKeyframes } = this

if (!element.current) return

this.removedTransforms = removeNonTranslationalTransform(element)

const finalKeyframe =
unresolvedKeyframes[unresolvedKeyframes.length - 1]

element.getValue(name, finalKeyframe).jump(finalKeyframe, false)
}

measureInitialState() {
const { element, unresolvedKeyframes, name } = this

Expand All @@ -149,10 +134,14 @@ export class DOMKeyframesResolver<
)

unresolvedKeyframes[0] = this.measuredOrigin
}

renderEndStyles() {
this.element.render()
// Set final key frame to measure after next render
const measureKeyframe =
unresolvedKeyframes[unresolvedKeyframes.length - 1]

if (measureKeyframe !== undefined) {
element.getValue(name, measureKeyframe).jump(measureKeyframe, false)
}
}

measureEndState() {
Expand All @@ -175,10 +164,6 @@ export class DOMKeyframesResolver<
this.finalKeyframe = finalKeyframe as T
}

if (name === "height" && this.suspendedScrollY !== undefined) {
window.scrollTo(0, this.suspendedScrollY)
}

// If we removed transform values, reapply them before the next render
if (this.removedTransforms?.length) {
this.removedTransforms.forEach(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,6 @@ export function removeNonTranslationalTransform(visualElement: VisualElement) {
}
})

// Apply changes to element before measurement
if (removedTransforms.length) visualElement.render()

return removedTransforms
}

Expand Down
56 changes: 42 additions & 14 deletions packages/framer-motion/src/render/utils/KeyframesResolver.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { frame } from "../../frameloop"
import { MotionValue } from "../../value"
import type { VisualElement } from "../VisualElement"
import { removeNonTranslationalTransform } from "../dom/utils/unit-conversion"

export type UnresolvedKeyframes<T extends string | number> = Array<T | null>

Expand All @@ -12,24 +13,50 @@ let anyNeedsMeasurement = false

function measureAllKeyframes() {
if (anyNeedsMeasurement) {
// Write
toResolve.forEach((resolver) => {
resolver.needsMeasurement && resolver.unsetTransforms()
const resolversToMeasure = Array.from(toResolve).filter(
(resolver: KeyframeResolver) => resolver.needsMeasurement
)
const elementsToMeasure = new Set(
resolversToMeasure.map((resolver) => resolver.element)
)
const transformsToRestore = new Map<
VisualElement,
[string, string | number][]
>()

/**
* Write pass
* If we're measuring elements we want to remove bounding box-changing transforms.
*/
elementsToMeasure.forEach((element: VisualElement<HTMLElement>) => {
const removedTransforms = removeNonTranslationalTransform(element)

if (!removedTransforms.length) return

transformsToRestore.set(
element,
removeNonTranslationalTransform(element)
)

element.render()
})

// Read
toResolve.forEach((resolver) => {
resolver.needsMeasurement && resolver.measureInitialState()
})
resolversToMeasure.forEach((resolver) => resolver.measureInitialState())

// Write
toResolve.forEach((resolver) => {
resolver.needsMeasurement && resolver.renderEndStyles()
elementsToMeasure.forEach((element: VisualElement<HTMLElement>) => {
element.render()
})

// Read
toResolve.forEach((resolver) => {
resolver.needsMeasurement && resolver.measureEndState()
resolversToMeasure.forEach((resolver) => resolver.measureEndState())

// Write
resolversToMeasure.forEach((resolver) => {
if (resolver.suspendedScrollY !== undefined) {
window.scrollTo(0, resolver.suspendedScrollY)
}
})
}

Expand Down Expand Up @@ -62,11 +89,12 @@ export type OnKeyframesResolved<T extends string | number> = (
) => void

export class KeyframeResolver<T extends string | number = any> {
protected element?: VisualElement<any>
protected unresolvedKeyframes: UnresolvedKeyframes<string | number>
name?: string

element?: VisualElement<any>
finalKeyframe?: T
suspendedScrollY?: number

protected unresolvedKeyframes: UnresolvedKeyframes<string | number>

private motionValue?: MotionValue<T>
private onComplete: OnKeyframesResolved<T>
Expand Down Expand Up @@ -173,7 +201,7 @@ export class KeyframeResolver<T extends string | number = any> {
}
}

unsetTransforms() {}
setFinalKeyframe() {}
measureInitialState() {}
renderEndStyles() {}
measureEndState() {}
Expand Down

0 comments on commit 7a3a9cd

Please sign in to comment.