Skip to content

Commit 54c92ad

Browse files
committed
Allow disabling of shared layout crossfade
1 parent 419e1cb commit 54c92ad

File tree

5 files changed

+57
-5
lines changed

5 files changed

+57
-5
lines changed
+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import { AnimatePresence, motion, MotionConfig } from "framer-motion"
2+
import { useState } from "react"
3+
4+
export const App = () => {
5+
const [state, setState] = useState(false)
6+
7+
return (
8+
<MotionConfig transition={{ duration: 10, ease: (p) => 0.25 }}>
9+
<div style={{ display: "flex", gap: 100 }}>
10+
<motion.div
11+
layoutId="box"
12+
style={{ width: 100, height: 100, background: "red" }}
13+
/>
14+
<AnimatePresence>
15+
{state && (
16+
<motion.div
17+
layoutId="box"
18+
layoutCrossfade={false}
19+
style={{
20+
width: 100,
21+
height: 100,
22+
background: "blue",
23+
}}
24+
/>
25+
)}
26+
</AnimatePresence>
27+
</div>
28+
<button onClick={() => setState(!state)}>Toggle</button>
29+
</MotionConfig>
30+
)
31+
}

packages/framer-motion/cypress/integration/layout.ts

+12
Original file line numberDiff line numberDiff line change
@@ -347,4 +347,16 @@ describe("Layout animation", () => {
347347
expect($count.textContent).to.equal("1")
348348
})
349349
})
350+
351+
it("Disabling crossfade works as expected", () => {
352+
cy.visit("?test=layout-crossfade")
353+
.wait(50)
354+
.get("button")
355+
.trigger("click")
356+
.wait(200)
357+
.get("#box")
358+
.should(([$box]: any) => {
359+
expect($box.style.opacity).to.equal("1")
360+
})
361+
})
350362
})

packages/framer-motion/src/motion/features/layout/types.ts

+8
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,12 @@ export interface LayoutProps {
9292
* perform scale correction on it.
9393
*/
9494
"data-framer-portal-id"?: string
95+
96+
/**
97+
* By default, shared layout elements will crossfade. By setting this
98+
* to `false`, this element will take its default opacity throughout the animation.
99+
*
100+
* @public
101+
*/
102+
layoutCrossfade?: boolean
95103
}

packages/framer-motion/src/motion/utils/use-visual-element.ts

+2
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ function createProjectionNode(
153153
dragConstraints,
154154
layoutScroll,
155155
layoutRoot,
156+
layoutCrossfade,
156157
} = props
157158

158159
visualElement.projection = new ProjectionNodeConstructor(
@@ -177,6 +178,7 @@ function createProjectionNode(
177178
*/
178179
animationType: typeof layout === "string" ? layout : "both",
179180
initialPromotionConfig,
181+
crossfade: layoutCrossfade,
180182
layoutScroll,
181183
layoutRoot,
182184
})

packages/framer-motion/src/projection/animation/mix-values.ts

+4-5
Original file line numberDiff line numberDiff line change
@@ -25,19 +25,18 @@ export function mixValues(
2525
if (shouldCrossfadeOpacity) {
2626
target.opacity = mixNumber(
2727
0,
28-
// TODO Reinstate this if only child
29-
lead.opacity !== undefined ? (lead.opacity as number) : 1,
28+
(lead.opacity as number) ?? 1,
3029
easeCrossfadeIn(progress)
3130
)
3231
target.opacityExit = mixNumber(
33-
follow.opacity !== undefined ? (follow.opacity as number) : 1,
32+
(follow.opacity as number) ?? 1,
3433
0,
3534
easeCrossfadeOut(progress)
3635
)
3736
} else if (isOnlyMember) {
3837
target.opacity = mixNumber(
39-
follow.opacity !== undefined ? (follow.opacity as number) : 1,
40-
lead.opacity !== undefined ? (lead.opacity as number) : 1,
38+
(follow.opacity as number) ?? 1,
39+
(lead.opacity as number) ?? 1,
4140
progress
4241
)
4342
}

0 commit comments

Comments
 (0)