Skip to content

Commit

Permalink
Ensuring optimised animations are cancelled before layout animations
Browse files Browse the repository at this point in the history
  • Loading branch information
mattgperry committed Feb 29, 2024
1 parent 0cc3e13 commit b9d5fb5
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 0 deletions.
131 changes: 131 additions & 0 deletions dev/optimized-appear/defer-handoff-layout.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<html>
<head>
<style>
body {
padding: 100px;
margin: 0;
}

#box {
width: 100px;
height: 100px;
background-color: #0077ff;
}

[data-layout-correct="false"] {
background: #dd1144 !important;
opacity: 1 !important;
}
</style>
</head>
<body>
<div id="root"></div>
<script src="../../node_modules/react/umd/react.development.js"></script>
<script src="../../node_modules/react-dom/umd/react-dom.development.js"></script>
<script src="../../node_modules/react-dom/umd/react-dom-server-legacy.browser.development.js"></script>
<script src="../../packages/framer-motion/dist/framer-motion.dev.js"></script>
<script src="../projection/script-assert.js"></script>

<script>
const {
motion,
animateStyle,
animate,
startOptimizedAppearAnimation,
optimizedAppearDataAttribute,
motionValue,
frame,
} = window.Motion
const { matchViewportBox } = window.Assert
const root = document.getElementById("root")

const duration = 0.5
const x = motionValue(0)

let isFirstFrame = true

function Component() {
const [top, setTop] = React.useState(0)
React.useEffect(() => {
setTimeout(() => {
setTop(100)
}, 250)
}, [])

return React.createElement(motion.div, {
id: "box",
initial: { x: 0, opacity: 0 },
animate: { x: 100, opacity: 1 },
transition: {
duration,
ease: "linear",
layout: { ease: () => 0, duration: 10 },
},
style: { x, top, position: "relative" },
layout: true,
onLayoutAnimationStart: () => {
requestAnimationFrame(() => {
const box = document.getElementById("box")
const { top } = box.getBoundingClientRect()

if (top !== 100) {
showError(
box,
`layout animation overridden by optimised animation`
)
}
})
},
onAnimationComplete: () => {
const box = document.getElementById("box")
const { left } = box.getBoundingClientRect()

if (left !== 200) {
showError(
box,
`optimised animation conflict with layout measurements`
)
}
},
[optimizedAppearDataAttribute]: "a",
children: "Content",
})
}

// Emulate server rendering of element
root.innerHTML = ReactDOMServer.renderToString(
React.createElement(Component)
)

// Start optimised opacity animation
startOptimizedAppearAnimation(
document.getElementById("box"),
"opacity",
[0, 1],
{
duration: duration * 1000,
ease: "linear",
}
)

// Start WAAPI animation
const animation = startOptimizedAppearAnimation(
document.getElementById("box"),
"transform",
["translateX(0px)", "translateX(100px)"],
{
duration: duration * 1000,
ease: "linear",
},
(animation) => {
setTimeout(() => {
ReactDOM.hydrateRoot(
root,
React.createElement(Component)
)
}, (duration * 1000) / 4)
}
)
</script>
</body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ export function handoffOptimizedAppearAnimation(

const cancelAnimation = () => {
appearAnimationStore.delete(storeId)

try {
animation.cancel()
} catch (error) {}
Expand Down
10 changes: 10 additions & 0 deletions packages/framer-motion/src/animation/optimized-appear/start.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ export function startOptimizedAppearAnimation(
animation: readyAnimation,
startTime: null,
})

if (!window.HandoffCancelAllAnimations) {
window.HandoffCancelAllAnimations = () => {
appearAnimationStore.forEach(({ animation }) => {
animation.cancel()
})
appearAnimationStore.clear()
window.HandoffCancelAllAnimations = undefined
}
}
}

const startAnimation = () => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,6 @@ declare global {
interface Window {
HandoffAppearAnimations?: HandoffFunction
HandoffComplete?: boolean
HandoffCancelAllAnimations?: VoidFunction
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import { steps } from "../../frameloop/frame"
import { noop } from "../../utils/noop"
import { time } from "../../frameloop/sync-time"
import { microtask } from "../../frameloop/microtask"
import { optimizedAppearDataAttribute } from "../../animation/optimized-appear/data-id"

const transformAxes = ["", "X", "Y", "Z"]

Expand Down Expand Up @@ -628,6 +629,9 @@ export function createProjectionNode<I>({
/**
* Write
*/
if (window.HandoffCancelAllAnimations) {
window.HandoffCancelAllAnimations()
}
this.nodes!.forEach(resetTransformStyle)

/**
Expand Down Expand Up @@ -1486,6 +1490,7 @@ export function createProjectionNode<I>({
cancelFrame(this.pendingAnimation)
this.pendingAnimation = undefined
}

/**
* Start the animation in the next frame to have a frame with progress 0,
* where the target is the same as when the animation started, so we can
Expand Down

0 comments on commit b9d5fb5

Please sign in to comment.