09Circle Morph

Circle Morph

GSAPClip-pathCreativeFuturistic

Morphing effect using CSS clip-path. Overlay expands as a circle from the center of the screen, covering the page, then shrinks to reveal new content. Portal effect.

Quick Start

  • 📦npm install next-transition-router gsap
  • 🌀Use clip-path: circle() for circular mask
  • 📐150% radius ensures full coverage on all screens
  • ✨Exit: circle expands | Enter: circle shrinks to reveal

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 MorphProvider({ children }: { children: React.ReactNode }) {
8 const overlayRef = useRef<HTMLDivElement>(null)
9 const wrapperRef = useRef<HTMLDivElement>(null)
10
11 return (
12 <>
13 <TransitionRouter
14 auto
15 leave={(next) => {
16 const tl = gsap.timeline({ onComplete: next })
17 const content = wrapperRef.current?.querySelectorAll('h1, p, .btn, .demo-badge')
18
19 // Content scales down toward center
20 tl.to(content, {
21 scale: 0.9,
22 opacity: 0,
23 stagger: 0.03,
24 duration: 0.3,
25 ease: 'power2.in',
26 })
27
28 // Circle morph expands from center
29 tl.fromTo(
30 overlayRef.current,
31 { clipPath: 'circle(0% at 50% 50%)' },
32 {
33 clipPath: 'circle(150% at 50% 50%)',
34 duration: 0.6,
35 ease: 'power4.inOut',
36 },
37 '-=0.15'
38 )
39 }}
40 enter={(next) => {
41 const tl = gsap.timeline({ onComplete: next })
42 const content = wrapperRef.current?.querySelectorAll('h1, p, .btn, .demo-badge')
43
44 // Set initial states
45 gsap.set(content, { scale: 0.9, opacity: 0 })
46
47 // Circle morph shrinks to reveal
48 tl.fromTo(
49 overlayRef.current,
50 { clipPath: 'circle(150% at 50% 50%)' },
51 {
52 clipPath: 'circle(0% at 50% 50%)',
53 duration: 0.6,
54 ease: 'power4.inOut',
55 }
56 )
57
58 // Content scales up and fades in
59 tl.to(content, {
60 scale: 1,
61 opacity: 1,
62 stagger: 0.05,
63 duration: 0.45,
64 ease: 'power3.out',
65 }, '-=0.25')
66 }}
67 >
68 <div ref={wrapperRef}>{children}</div>
69 </TransitionRouter>
70
71 <div
72 ref={overlayRef}
73 style={{
74 position: 'fixed',
75 inset: 0,
76 background: '#0a0a0a',
77 zIndex: 9999,
78 clipPath: 'circle(0% at 50% 50%)',
79 }}
80 />
81 </>
82 )
83}