Skip to content

Commit

Permalink
Auto will-change (#2700)
Browse files Browse the repository at this point in the history
* Removing translateZ(0)

* Fixing tests

* Debug

* Defining expected behaviour

* Adding tests

* Fixing tests

* Latest

* Latest

* Latest

* Allowing willChange to be manullly set by string

* Fixing tests

* Latest

* Updating changes

* Removing logs:

* Latest

* Latest

* Latest

* Fixing tests

* Update

* Increasing test timeout

* Fixing failing tesst

* Removing

* Fixing tests

* Improving comments

* Updating types

* Fixing types

* Adding will-change events

* Removing event listener approach

* Adding comment

* Updating

* Upping bundlesize

* Speeding up code

* Adding tests for static mode
  • Loading branch information
mattgperry authored Jul 10, 2024
1 parent 250f4e6 commit 4341943
Show file tree
Hide file tree
Showing 55 changed files with 772 additions and 594 deletions.
63 changes: 12 additions & 51 deletions dev/react/src/examples/Animation-animate.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { motion } from "framer-motion"
import { useEffect, useState } from "react"
import { motion, motionValue, useAnimate } from "framer-motion"
import { frame } from "framer-motion"

/**
* An example of the tween transition type
Expand All @@ -11,56 +10,18 @@ const style = {
height: 100,
background: "white",
}

const Child = ({ setState }: any) => {
const [width] = useState(100)
const [target, setTarget] = useState(0)
const transition = {
duration: 10,
}

const [scope, animate] = useAnimate()

export const App = () => {
const [state, setState] = useState(false)
useEffect(() => {
const controls = animate([
[
"div",
{ x: 500, opacity: 0 },
{ type: "spring", duration: 1, bounce: 0 },
],
])

controls.then(() => {
controls.play()
})

return () => controls.stop()
}, [target])

setTimeout(() => {
setState(true)
}, 300)
})
return (
<div ref={scope}>
<motion.div
id="box"
style={{
x: target,
...style,
width: motionValue(width),
y: width / 10,
}}
onClick={() => {
setTarget(target + 100)
// setWidth(width + 100)
}}
initial={{ borderRadius: 10 }}
/>
{/* <div style={style} onClick={() => setState(false)} /> */}
</div>
<motion.div
animate={{ x: state ? 0 : 100 }}
transition={{ duration: 1 }}
style={style}
/>
)
return
}

export const App = () => {
const [state, setState] = useState(true)

return state && <Child setState={setState} />
}
2 changes: 1 addition & 1 deletion dev/react/src/examples/Animation-stagger-custom.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, useEffect } from "react";
import { useState, useEffect } from "react"
import { useAnimation, distance2D, wrap } from "framer-motion"
import { motion } from "framer-motion"

Expand Down
10 changes: 5 additions & 5 deletions packages/framer-motion/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,23 +85,23 @@
"bundlesize": [
{
"path": "./dist/size-rollup-motion.js",
"maxSize": "33.45 kB"
"maxSize": "33.72 kB"
},
{
"path": "./dist/size-rollup-m.js",
"maxSize": "5.85 kB"
"maxSize": "6 kB"
},
{
"path": "./dist/size-rollup-dom-animation.js",
"maxSize": "16.88 kB"
"maxSize": "17 kB"
},
{
"path": "./dist/size-rollup-dom-max.js",
"maxSize": "28.6 kB"
"maxSize": "28.8 kB"
},
{
"path": "./dist/size-rollup-animate.js",
"maxSize": "17.86 kB"
"maxSize": "18 kB"
}
],
"gitHead": "3a6a6e20deb697df3a20201607a48150e2d77255"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,7 @@ describe("animate", () => {

const [value, element] = await promise
expect(value.get()).toBe(200)
expect(element).toHaveStyle(
"transform: translateX(200px) translateZ(0)"
)
expect(element).toHaveStyle("transform: translateX(200px)")
})

test("correctly animates normal values", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,16 @@ describe("css variables", () => {

const results = await promise
expect(results).toEqual([
{ "--a": "20px", "--color": "rgba(0, 0, 0, 1)" },
{ "--a": "20px", "--color": "rgba(0, 0, 0, 1)" },
{
"--a": "20px",
"--color": "rgba(0, 0, 0, 1)",
willChange: "auto",
},
{
"--a": "20px",
"--color": "rgba(0, 0, 0, 1)",
willChange: "auto",
},
])
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ describe("useAnimation", () => {
}
const { container } = render(<Component />)
expect(container.firstChild as HTMLElement).toHaveStyle(
"transform: translateX(10px) translateZ(0); background: rgb(255, 255, 255)"
"transform: translateX(10px); background: rgb(255, 255, 255)"
)
})

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
ValueAnimationOptionsWithDefaults,
} from "./BaseAnimation"
import { MainThreadAnimation } from "./MainThreadAnimation"
import { acceleratedValues } from "./utils/accelerated-values"
import { animateStyle } from "./waapi"
import { isWaapiSupportedEasing } from "./waapi/easing"
import { getFinalKeyframe } from "./waapi/utils/get-final-keyframe"
Expand All @@ -23,19 +24,6 @@ const supportsWaapi = memo(() =>
Object.hasOwnProperty.call(Element.prototype, "animate")
)

/**
* A list of values that can be hardware-accelerated.
*/
const acceleratedValues = new Set<string>([
"opacity",
"clipPath",
"filter",
"transform",
// TODO: Can be accelerated but currently disabled until https://issues.chromium.org/issues/41491098 is resolved
// or until we implement support for linear() easing.
// "background-color"
])

/**
* 10ms is chosen here as it strikes a balance between smooth
* results (more than one keyframe per frame at 60fps) and
Expand Down Expand Up @@ -372,6 +360,9 @@ export class AcceleratedAnimation<
)
}

const { onStop } = this.options
onStop && onStop()

this.cancel()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -489,7 +489,6 @@ export class MainThreadAnimation<
this.resolver.cancel()
this.isStopped = true
if (this.state === "idle") return

this.teardown()
const { onStop } = this.options
onStop && onStop()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* A list of values that can be hardware-accelerated.
*/
export const acceleratedValues = new Set<string>([
"opacity",
"clipPath",
"filter",
"transform",
// TODO: Can be accelerated but currently disabled until https://issues.chromium.org/issues/41491098 is resolved
// or until we implement support for linear() easing.
// "background-color"
])
11 changes: 10 additions & 1 deletion packages/framer-motion/src/animation/interfaces/motion-value.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ export const animateMotionValue =
target: V | UnresolvedKeyframes<V>,
transition: Transition & { elapsed?: number } = {},
element?: VisualElement<any>,
isHandoff?: boolean
isHandoff?: boolean,
/**
* Currently used to remove values from will-change when an animation ends.
* Preferably this would be handled by event listeners on the MotionValue
* but these aren't consistent enough yet when considering the different ways
* an animation can be cancelled.
*/
onEnd?: VoidFunction
): StartAnimation =>
(onComplete): AnimationPlaybackControls => {
const valueTransition = getValueTransition(transition, name) || {}
Expand Down Expand Up @@ -53,7 +60,9 @@ export const animateMotionValue =
onComplete: () => {
onComplete()
valueTransition.onComplete && valueTransition.onComplete()
onEnd && onEnd()
},
onStop: onEnd,
name,
motionValue: value,
element: isHandoff ? undefined : element,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import type { VisualElement } from "../../render/VisualElement"
import type { TargetAndTransition } from "../../types"
import type { VisualElementAnimationOptions } from "./types"
import { animateMotionValue } from "./motion-value"
import { isWillChangeMotionValue } from "../../value/use-will-change/is"
import { setTarget } from "../../render/utils/setters"
import { AnimationPlaybackControls } from "../types"
import { getValueTransition } from "../utils/transitions"
import { frame } from "../../frameloop"
import { getOptimisedAppearId } from "../optimized-appear/get-appear-id"
import { addValueToWillChange } from "../../value/use-will-change/add-will-change"

/**
* Decide whether we should block this animation. Previously, we achieved this
Expand Down Expand Up @@ -39,8 +39,6 @@ export function animateTarget(
...target
} = targetAndTransition

const willChange = visualElement.getValue("willChange")

if (transitionOverride) transition = transitionOverride

const animations: AnimationPlaybackControls[] = []
Expand Down Expand Up @@ -103,18 +101,14 @@ export function animateTarget(
? { type: false }
: valueTransition,
visualElement,
isHandoff
isHandoff,
addValueToWillChange(visualElement, key)
)
)

const animation = value.animation

if (animation) {
if (isWillChangeMotionValue(willChange)) {
willChange.add(key)
animation.then(() => willChange.remove(key))
}

animations.push(animation)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,8 @@ export function createVisualElement(element: HTMLElement | SVGElement) {
},
}
const node = isSVGElement(element)
? new SVGVisualElement(options, {
enableHardwareAcceleration: false,
})
: new HTMLVisualElement(options, {
enableHardwareAcceleration: true,
})
? new SVGVisualElement(options)
: new HTMLVisualElement(options)

node.mount(element as any)

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { render } from "../../../../jest.setup"
import { createRef } from "react";
import { createRef } from "react"
import { act } from "react-dom/test-utils"
import {
AnimatePresence,
Expand Down Expand Up @@ -68,9 +68,7 @@ describe("AnimatePresence", () => {
})

const element = await promise
expect(element).toHaveStyle(
"transform: translateX(100px) translateZ(0)"
)
expect(element).toHaveStyle("transform: translateX(100px)")
})

test("Animates out a component when its removed", async () => {
Expand Down Expand Up @@ -646,9 +644,7 @@ describe("AnimatePresence with custom components", () => {
})

const element = await promise
expect(element).toHaveStyle(
"transform: translateX(100px) translateZ(0)"
)
expect(element).toHaveStyle("transform: translateX(100px)")
})

test("Animation controls children of initial={false} don't throw`", async () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ describe("Reorder", () => {
const staticMarkup = renderToStaticMarkup(<Component />)
const string = renderToString(<Component />)

const expectedMarkup = `<article><main style="z-index:unset;transform:none;-webkit-touch-callout:none;-webkit-user-select:none;user-select:none;touch-action:pan-x" draggable="false"></main></article>`
const expectedMarkup = `<article><main style="z-index:unset;will-change:transform;transform:none;-webkit-touch-callout:none;-webkit-user-select:none;user-select:none;touch-action:pan-x" draggable="false"></main></article>`

expect(staticMarkup).toBe(expectedMarkup)
expect(string).toBe(expectedMarkup)
Expand All @@ -32,7 +32,7 @@ describe("Reorder", () => {
const staticMarkup = renderToStaticMarkup(<Component />)
const string = renderToString(<Component />)

const expectedMarkup = `<article><main style="z-index:unset;transform:none;-webkit-touch-callout:none;-webkit-user-select:none;user-select:none;touch-action:pan-x" draggable="false"></main></article>`
const expectedMarkup = `<article><main style="z-index:unset;will-change:transform;transform:none;-webkit-touch-callout:none;-webkit-user-select:none;user-select:none;touch-action:pan-x" draggable="false"></main></article>`

expect(staticMarkup).toBe(expectedMarkup)
expect(string).toBe(expectedMarkup)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { percent } from "../../value/types/numbers/units"
import { animateMotionValue } from "../../animation/interfaces/motion-value"
import { getContextWindow } from "../../utils/get-context-window"
import { frame } from "../../frameloop"
import { addValueToWillChange } from "../../value/use-will-change/add-will-change"

export const elementDragControls = new WeakMap<
VisualElement,
Expand Down Expand Up @@ -78,6 +79,8 @@ export class VisualElementDragControls {
*/
private elastic = createBox()

private removeWillChange: VoidFunction | undefined

constructor(visualElement: VisualElement<HTMLElement>) {
this.visualElement = visualElement
}
Expand Down Expand Up @@ -157,6 +160,12 @@ export class VisualElementDragControls {
frame.postRender(() => onDragStart(event, info))
}

this.removeWillChange?.()
this.removeWillChange = addValueToWillChange(
this.visualElement,
"transform"
)

const { animationState } = this.visualElement
animationState && animationState.setActive("whileDrag", true)
}
Expand Down Expand Up @@ -235,6 +244,8 @@ export class VisualElementDragControls {
}

private stop(event: PointerEvent, info: PanInfo) {
this.removeWillChange?.()

const isDragging = this.isDragging
this.cancel()
if (!isDragging) return
Expand Down Expand Up @@ -443,13 +454,16 @@ export class VisualElementDragControls {
transition: Transition
) {
const axisValue = this.getAxisMotionValue(axis)

return axisValue.start(
animateMotionValue(
axis,
axisValue,
0,
transition,
this.visualElement
this.visualElement,
false,
addValueToWillChange(this.visualElement, axis)
)
)
}
Expand Down
Loading

0 comments on commit 4341943

Please sign in to comment.