A staggered clip-path sweep that reveals the page beneath an overlay.
npx atelier-ui add sweep-exitnpm install motion"use client"
import type { Easing } from "motion"
import { stagger, useAnimate } from "motion/react"
import { type ReactNode, useEffect, useRef } from "react"
const EASE: Easing = [0.85, 0, 0.2, 1]
export type SweepExitProps = {
children?: ReactNode
backgroundSlot: ReactNode
duration?: number
insetX?: number
insetY?: number
trigger?: boolean
onComplete?: () => void
}
export function SweepExit({
children,
backgroundSlot,
duration = 1,
insetX = 20,
insetY = 10,
trigger = true,
onComplete,
}: SweepExitProps) {
const [scope, animate] = useAnimate()
const configRef = useRef({ duration, onComplete })
configRef.current = { duration, onComplete }
useEffect(() => {
if (!trigger) return
let cancelled = false
let controls: ReturnType<typeof animate> | undefined
async function run() {
const { duration } = configRef.current
controls = animate([
[
".aui-clip, .aui-clip > :not(.aui-clip-bg)",
{
clipPath: [
"inset(0% 0% 0% 0%)",
`inset(${insetY}% ${insetX}% ${insetY}% ${insetX}%)`,
],
},
{ duration: 1.3 * duration, ease: EASE },
],
[
".aui-clip",
{
scale: [1, 0.9],
},
{ duration: 1.3 * duration, ease: EASE, at: 0 },
],
[
".aui-bg-overlay",
{
scale: [2, 1],
},
{ duration: 1.8 * duration, ease: EASE, at: 0 },
],
[
".aui-clip, .aui-clip > :not(.aui-clip-bg)",
{ clipPath: `inset(${insetY}% ${insetX}% ${100 - insetY}% ${insetX}%)` },
{
duration: 1 * duration,
ease: EASE,
at: 1.3 * duration,
delay: stagger(0.05 * duration, { from: "last" }),
},
],
[
".aui-bg-overlay",
{
clipPath: `inset(0% 0% 100% 0%)`,
},
{ duration: 3 * duration, ease: EASE, at: 0 },
],
])
await controls
if (!cancelled) {
configRef.current.onComplete?.()
}
}
run()
return () => {
cancelled = true
controls?.stop()
}
}, [trigger, animate, insetY, insetX])
return (
<div ref={scope}>
<div className="aui-bg-overlay fixed inset-0 z-50 w-screen h-screen overflow-hidden">
{backgroundSlot}
</div>
<div className="aui-clip fixed inset-0 z-[60] overflow-hidden origin-center">
<div className="aui-clip-bg absolute inset-0 -z-10">{backgroundSlot}</div>
{children}
</div>
</div>
)
}
## Integrate the <SweepExit /> component from Atelier UI
You are helping integrate an open-source React component into an existing application.
### Component: SweepExit
### Description: A staggered clip-path sweep that reveals the page beneath an overlay.
### Dependencies: motion
---
### Usage Example
`backgroundSlot` is required: it holds the overlay shown before the sweep reveals the children beneath.
```tsx
<SweepExit
backgroundSlot={
<img src="/overlay.jpg" alt="" className="absolute inset-0 h-full w-full object-cover" />
}
>
<YourPage />
</SweepExit>
```
---
### Props
| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `children` | `ReactNode` | — | Content rendered on top of the overlay layer. |
| `backgroundSlot` | `ReactNode` | — | Content rendered in the background overlay. Required. |
| `duration` | `number` | `1` | Time-scale multiplier applied to every sub-step. |
| `insetX` | `number` | `20` | Horizontal inset at peak contraction in percent. |
| `insetY` | `number` | `10` | Vertical inset at peak contraction in percent. |
| `trigger` | `boolean` | `true` | Starts the animation when set to true. |
| `onComplete` | `() => void` | — | Called when the animation finishes. |
---
### Full Component Source
#### src/components/sweep-exit/sweep-exit.tsx
```tsx
"use client"
import type { Easing } from "motion"
import { stagger, useAnimate } from "motion/react"
import { type ReactNode, useEffect, useRef } from "react"
const EASE: Easing = [0.85, 0, 0.2, 1]
export type SweepExitProps = {
children?: ReactNode
backgroundSlot: ReactNode
duration?: number
insetX?: number
insetY?: number
trigger?: boolean
onComplete?: () => void
}
export function SweepExit({
children,
backgroundSlot,
duration = 1,
insetX = 20,
insetY = 10,
trigger = true,
onComplete,
}: SweepExitProps) {
const [scope, animate] = useAnimate()
const configRef = useRef({ duration, onComplete })
configRef.current = { duration, onComplete }
useEffect(() => {
if (!trigger) return
let cancelled = false
let controls: ReturnType<typeof animate> | undefined
async function run() {
const { duration } = configRef.current
controls = animate([
[
".aui-clip, .aui-clip > :not(.aui-clip-bg)",
{
clipPath: [
"inset(0% 0% 0% 0%)",
`inset(${insetY}% ${insetX}% ${insetY}% ${insetX}%)`,
],
},
{ duration: 1.3 * duration, ease: EASE },
],
[
".aui-clip",
{
scale: [1, 0.9],
},
{ duration: 1.3 * duration, ease: EASE, at: 0 },
],
[
".aui-bg-overlay",
{
scale: [2, 1],
},
{ duration: 1.8 * duration, ease: EASE, at: 0 },
],
[
".aui-clip, .aui-clip > :not(.aui-clip-bg)",
{ clipPath: `inset(${insetY}% ${insetX}% ${100 - insetY}% ${insetX}%)` },
{
duration: 1 * duration,
ease: EASE,
at: 1.3 * duration,
delay: stagger(0.05 * duration, { from: "last" }),
},
],
[
".aui-bg-overlay",
{
clipPath: `inset(0% 0% 100% 0%)`,
},
{ duration: 3 * duration, ease: EASE, at: 0 },
],
])
await controls
if (!cancelled) {
configRef.current.onComplete?.()
}
}
run()
return () => {
cancelled = true
controls?.stop()
}
}, [trigger, animate, insetY, insetX])
return (
<div ref={scope}>
<div className="aui-bg-overlay fixed inset-0 z-50 w-screen h-screen overflow-hidden">
{backgroundSlot}
</div>
<div className="aui-clip fixed inset-0 z-[60] overflow-hidden origin-center">
<div className="aui-clip-bg absolute inset-0 -z-10">{backgroundSlot}</div>
{children}
</div>
</div>
)
}
```
---
### Integration Instructions
1. If you can execute shell commands, run `npx atelier-ui add sweep-exit` from the project root instead of steps 2-3 (it installs everything automatically).
2. Install the npm dependencies: motion.
3. Copy each file from the component source above to the exact path shown.
4. Render `<SweepExit />` where it belongs in the app, using the usage example as a starting point and the props table to adjust it.
Full documentation: https://atelier-ui.com/en/docs/components/transition/sweep-exitbackgroundSlot is required: it holds the overlay shown before the sweep reveals the children beneath.
<SweepExit
backgroundSlot={
<img src="/overlay.jpg" alt="" className="absolute inset-0 h-full w-full object-cover" />
}
>
<YourPage />
</SweepExit>| Name | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Content rendered on top of the overlay layer. |
backgroundSlot | ReactNode | — | Content rendered in the background overlay. Required. |
duration | number | 1 | Time-scale multiplier applied to every sub-step. |
insetX | number | 20 | Horizontal inset at peak contraction in percent. |
insetY | number | 10 | Vertical inset at peak contraction in percent. |
trigger | boolean | true | Starts the animation when set to true. |
onComplete | () => void | — | Called when the animation finishes. |
Zajno
Page transition that inspired this effect.
Motion
React animation library.