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

Optimised appear animations #1816

Merged
merged 16 commits into from Dec 13, 2022
Merged
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -16,6 +16,7 @@ yarn-error.log
/packages/*/coverage
/packages/*/cypress/screenshots
/packages/*/cypress/videos
/packages/*/cypress/fixtures/appear-tests.json
/packages/*/cypress/fixtures/projection-tests.json
.cache-loader

Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
19 changes: 19 additions & 0 deletions dev/optimized-appear/collect-appear-tests.js
@@ -0,0 +1,19 @@
const fs = require("fs")
const path = require("path")

const files = fs
.readdirSync(__dirname)
.filter((f) => path.extname(f) === ".html" && !f.includes(".skip."))

fs.writeFile(
"../../packages/framer-motion/cypress/fixtures/appear-tests.json",
JSON.stringify(files),
"utf8",
(err) => {
if (err) {
return console.error("Fail to collect appear tests:", err.message)
}

console.log("Appear tests collected!")
}
)
127 changes: 127 additions & 0 deletions dev/optimized-appear/interrupt-delay-after.html
@@ -0,0 +1,127 @@
<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,
startOptimizedAppearAnimation,
optimizedAppearDataAttribute,
motionValue,
} = window.Motion
const { matchOpacity } = window.Assert
const root = document.getElementById("root")

const duration = 1
const opacity = motionValue(0)
let opacityHasChanged = false

opacity.onChange((v) => {
if (!opacityHasChanged) {
if (v > 0.3) {
showError(
document.getElementById("box"),
`opacity should not start animating beyond 0.3 (started at ${v})`
)
}
}
opacityHasChanged = true
if (v < 0.25) {
showError(
document.getElementById("box"),
"opacity should never be less than 0.25"
)
}
})

// This is the tree to be rendered "server" and client-side.
const Component = React.createElement(motion.div, {
id: "box",
initial: { opacity: 0 },
animate: { opacity: 1 },
transition: { duration, ease: "linear", delay: 0.25 },
style: { opacity },
/**
* On animation start, check the values we expect to see here
*/
onAnimationStart: () => {
matchOpacity(document.getElementById("box"), 0.25)
},
[optimizedAppearDataAttribute]: "a",
})

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

// Start Motion One animation
const animation = startOptimizedAppearAnimation(
document.getElementById("box"),
"opacity",
[0, 1],
{
duration: duration * 1000,
ease: "linear",
delay: 250,
}
)

const ready = animation.ready
? animation.ready.then
: requestAnimationFrame
ready(() => {
/**
* Set currentTime to 500ms - because of the delay this will cut
* into the animation at 0.25
*/
if (animation) {
animation.currentTime = (duration * 1000) / 2
animation.pause()
}

// Hydrate root mid-way through animation
ReactDOM.hydrateRoot(root, Component)

/**
* Check the animation isn't in its initial state
*/
setTimeout(() => {
const { opacity: initialOpacity } = window.getComputedStyle(
document.getElementById("box")
)

if (initialOpacity === "0") {
showError(
document.getElementById("box"),
`opacity should have animated`
)
}
}, 100)
})
</script>
</body>
</html>
104 changes: 104 additions & 0 deletions dev/optimized-appear/interrupt-delay-before.html
@@ -0,0 +1,104 @@
<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,
startOptimizedAppearAnimation,
optimizedAppearDataAttribute,
motionValue,
} = window.Motion
const { matchOpacity } = window.Assert
const root = document.getElementById("root")

const duration = 1
const opacity = motionValue(0)
let opacityHasChanged = false
opacity.onChange((v) => {
if (!opacityHasChanged) {
if (v > 0.3) {
showError(
document.getElementById("box"),
`opacity should not start animating beyond 0 (started at ${v})`
)
}
}
opacityHasChanged = true
})

// This is the tree to be rendered "server" and client-side.
const Component = React.createElement(motion.div, {
id: "box",
initial: { opacity: 0 },
animate: { opacity: 1 },
transition: { duration, ease: "linear", delay: 0.25 },
style: { opacity },
/**
* On animation start, check the values we expect to see here
*/
onAnimationStart: () => {
matchOpacity(document.getElementById("box"), 0)
},
[optimizedAppearDataAttribute]: "a",
})

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

// Start Motion One animation
const animation = startOptimizedAppearAnimation(
document.getElementById("box"),
"opacity",
[0, 1],
{
duration: duration * 1000,
ease: "linear",
delay: 250,
}
)

const ready = animation.ready
? animation.ready.then
: requestAnimationFrame
ready(() => {
/**
* Set currentTime to 500ms - because of the delay this will cut
* into the animation at 0.25
*/
if (animation) {
animation.currentTime = 200
animation.pause()
}

// Hydrate root mid-way through animation
ReactDOM.hydrateRoot(root, Component)
})
</script>
</body>
</html>