08Curtain Effect

Curtain Effect

GSAPTimelineTheatricalDramatic

Theatrical curtain effect. Two panels close from the sides, covering content, then open to reveal the new page. Dramatic effect ideal for events and product launches.

Quick Start

  • 📦npm install next-transition-router gsap
  • 🎭Two curtain panels with position: fixed
  • ⚡Use '<' marker for synchronized curtain movement
  • ✨Content fades before curtains close, reveals after they open

Installation

Terminal
1npm install next-transition-router gsap

Provider Setup

app/providers.tsx
1'use client'
2
3import { useRef } from 'react'
4import { TransitionRouter } from 'next-transition-router'
5import gsap from 'gsap'
6
7export default function CurtainProvider({ children }: { children: React.ReactNode }) {
8 const leftCurtainRef = useRef<HTMLDivElement>(null)
9 const rightCurtainRef = useRef<HTMLDivElement>(null)
10 const wrapperRef = useRef<HTMLDivElement>(null)
11
12 return (
13 <>
14 <TransitionRouter
15 auto
16 leave={(next) => {
17 const tl = gsap.timeline({ onComplete: next })
18 const content = wrapperRef.current?.querySelectorAll('h1, p, .btn, .demo-badge')
19
20 // Content fades first
21 tl.to(content, {
22 opacity: 0,
23 scale: 0.95,
24 stagger: 0.03,
25 duration: 0.25,
26 ease: 'power2.in',
27 })
28
29 // Curtains close from sides
30 tl.to(leftCurtainRef.current, {
31 x: '0%',
32 duration: 0.45,
33 ease: 'power4.inOut',
34 }, '-=0.1')
35 tl.to(rightCurtainRef.current, {
36 x: '0%',
37 duration: 0.45,
38 ease: 'power4.inOut',
39 }, '<') // '<' = start at same time
40 }}
41 enter={(next) => {
42 const tl = gsap.timeline({ onComplete: next })
43 const content = wrapperRef.current?.querySelectorAll('h1, p, .btn, .demo-badge')
44
45 // Set initial content state
46 gsap.set(content, { opacity: 0, scale: 0.95, y: 30 })
47
48 // Curtains open
49 tl.to(leftCurtainRef.current, {
50 x: '-100%',
51 duration: 0.5,
52 ease: 'power4.inOut',
53 })
54 tl.to(rightCurtainRef.current, {
55 x: '100%',
56 duration: 0.5,
57 ease: 'power4.inOut',
58 }, '<')
59
60 // Content reveals
61 tl.to(content, {
62 opacity: 1,
63 scale: 1,
64 y: 0,
65 stagger: 0.06,
66 duration: 0.45,
67 ease: 'power3.out',
68 }, '-=0.25')
69 }}
70 >
71 <div ref={wrapperRef}>{children}</div>
72 </TransitionRouter>
73
74 {/* Left Curtain */}
75 <div
76 ref={leftCurtainRef}
77 style={{
78 position: 'fixed',
79 top: 0,
80 left: 0,
81 width: '50%',
82 height: '100%',
83 background: '#0a0a0a',
84 zIndex: 9999,
85 transform: 'translateX(-100%)',
86 }}
87 />
88
89 {/* Right Curtain */}
90 <div
91 ref={rightCurtainRef}
92 style={{
93 position: 'fixed',
94 top: 0,
95 right: 0,
96 width: '50%',
97 height: '100%',
98 background: '#0a0a0a',
99 zIndex: 9999,
100 transform: 'translateX(100%)',
101 }}
102 />
103 </>
104 )
105}