22 CSS Transition Effects 12 / 22
Page Transition Effect
Four pages with four transition types each: horizontal wipe, curtain, circle burst and vertical-slices stagger — with type selector and dot nav.
This is a full-page demo — interact inside the frame above, or open it in the playground for the full-screen experience.
The code
<div class="type-select" id="typeSelect">
<button class="ts-btn active" data-type="wipe">Wipe</button>
<button class="ts-btn" data-type="curtain">Curtain</button>
<button class="ts-btn" data-type="circle">Circle</button>
<button class="ts-btn" data-type="slices">Slices</button>
</div>
<div class="page-app">
<div class="page pg1 active" data-page="0">
<div class="page-num">01 / 04</div>
<h2>Page Transitions</h2>
<p>CSS-powered page transition effects. Choose a style above, then click Next to see it play.</p>
<button class="next-btn" onclick="nextPage()">Next page →</button>
</div>
<div class="page pg2" data-page="1">
<div class="page-num">02 / 04</div>
<h2>Smooth Moves</h2>
<p>Four transition types: horizontal wipe, curtain open, radial circle burst, and vertical slices.</p>
<button class="next-btn" onclick="nextPage()">Next page →</button>
</div>
<div class="page pg3" data-page="2">
<div class="page-num">03 / 04</div>
<h2>Full Coverage</h2>
<p>Each effect uses pure CSS animations on a fixed overlay that covers the viewport during the swap.</p>
<button class="next-btn" onclick="nextPage()">Next page →</button>
</div>
<div class="page pg4" data-page="3">
<div class="page-num">04 / 04</div>
<h2>Cycle Back</h2>
<p>Click below to loop back to the start, or select a different transition type above.</p>
<button class="next-btn" onclick="nextPage()">Back to start →</button>
</div>
</div>
<div class="t-overlay" id="overlay">
<div class="t-wipe" style="position:absolute;inset:0;transform:translateX(-100%);--c:#a78bfa"></div>
<div class="t-curtain-l" style="--c:#f97316;transform:translateX(-100%)"></div>
<div class="t-curtain-r" style="--c:#f97316;transform:translateX(100%)"></div>
<div class="t-circle" style="width:200vmax;height:200vmax;--c:#10b981"></div>
<div class="t-slices" style="--c:#38bdf8">
<div class="t-slice" style="transform:scaleY(0);transform-origin:top"></div>
<div class="t-slice" style="transform:scaleY(0);transform-origin:top"></div>
<div class="t-slice" style="transform:scaleY(0);transform-origin:top"></div>
<div class="t-slice" style="transform:scaleY(0);transform-origin:top"></div>
<div class="t-slice" style="transform:scaleY(0);transform-origin:top"></div>
</div>
</div>
<div class="dots" id="dots"></div> <div class="type-select" id="typeSelect">
<button class="ts-btn active" data-type="wipe">Wipe</button>
<button class="ts-btn" data-type="curtain">Curtain</button>
<button class="ts-btn" data-type="circle">Circle</button>
<button class="ts-btn" data-type="slices">Slices</button>
</div>
<div class="page-app">
<div class="page pg1 active" data-page="0">
<div class="page-num">01 / 04</div>
<h2>Page Transitions</h2>
<p>CSS-powered page transition effects. Choose a style above, then click Next to see it play.</p>
<button class="next-btn" onclick="nextPage()">Next page →</button>
</div>
<div class="page pg2" data-page="1">
<div class="page-num">02 / 04</div>
<h2>Smooth Moves</h2>
<p>Four transition types: horizontal wipe, curtain open, radial circle burst, and vertical slices.</p>
<button class="next-btn" onclick="nextPage()">Next page →</button>
</div>
<div class="page pg3" data-page="2">
<div class="page-num">03 / 04</div>
<h2>Full Coverage</h2>
<p>Each effect uses pure CSS animations on a fixed overlay that covers the viewport during the swap.</p>
<button class="next-btn" onclick="nextPage()">Next page →</button>
</div>
<div class="page pg4" data-page="3">
<div class="page-num">04 / 04</div>
<h2>Cycle Back</h2>
<p>Click below to loop back to the start, or select a different transition type above.</p>
<button class="next-btn" onclick="nextPage()">Back to start →</button>
</div>
</div>
<div class="t-overlay" id="overlay">
<div class="t-wipe" style="position:absolute;inset:0;transform:translateX(-100%);--c:#a78bfa"></div>
<div class="t-curtain-l" style="--c:#f97316;transform:translateX(-100%)"></div>
<div class="t-curtain-r" style="--c:#f97316;transform:translateX(100%)"></div>
<div class="t-circle" style="width:200vmax;height:200vmax;--c:#10b981"></div>
<div class="t-slices" style="--c:#38bdf8">
<div class="t-slice" style="transform:scaleY(0);transform-origin:top"></div>
<div class="t-slice" style="transform:scaleY(0);transform-origin:top"></div>
<div class="t-slice" style="transform:scaleY(0);transform-origin:top"></div>
<div class="t-slice" style="transform:scaleY(0);transform-origin:top"></div>
<div class="t-slice" style="transform:scaleY(0);transform-origin:top"></div>
</div>
</div>
<div class="dots" id="dots"></div>@import url('https://fonts.googleapis.com/css2?family=Lexend:wght@300;400;600;700;800&display=swap');
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Lexend',sans-serif;background:#0d0d14;color:#f1f5f9;min-height:100vh;overflow:hidden}
/* page wrapper */
.page-app{width:100%;height:100vh;position:relative}
/* pages */
.page{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 24px;text-align:center;opacity:0;pointer-events:none;transition:opacity .01s 1s}
.page.active{opacity:1;pointer-events:auto;transition:opacity .01s}
.page h2{font-size:clamp(2.5rem,8vw,5rem);font-weight:800;margin-bottom:16px;letter-spacing:-.04em}
.page p{font-size:1rem;max-width:50ch;opacity:.6;line-height:1.6;margin-bottom:40px}
.page-num{font-size:.7rem;letter-spacing:.3em;text-transform:uppercase;opacity:.4;margin-bottom:16px}
.next-btn{padding:14px 36px;border-radius:30px;border:2px solid rgba(255,255,255,.2);background:transparent;color:#fff;font-family:'Lexend';font-weight:600;font-size:.9rem;cursor:pointer;transition:background .2s,border-color .2s}
.next-btn:hover{background:rgba(255,255,255,.1);border-color:rgba(255,255,255,.4)}
/* page backgrounds */
.pg1{background:linear-gradient(135deg,#0f0c29,#302b63,#24243e)}
.pg2{background:linear-gradient(135deg,#0a2e1a,#116545,#0d4a32)}
.pg3{background:linear-gradient(135deg,#2d0518,#7b0d2b,#4a0a1e)}
.pg4{background:linear-gradient(135deg,#0d1a3a,#1a3a6b,#0a2550)}
/* TRANSITION OVERLAY */
.t-overlay{position:fixed;inset:0;z-index:200;pointer-events:none}
/* Wipe variants */
.t-wipe{background:var(--c,#a78bfa)}
/* Curtain pair */
.t-curtain-l,.t-curtain-r{position:absolute;top:0;bottom:0;width:51%;background:var(--c,#f97316)}
.t-curtain-l{left:0}
.t-curtain-r{right:0}
/* Circle burst */
.t-circle{position:absolute;top:50%;left:50%;border-radius:50%;background:var(--c,#10b981);transform:translate(-50%,-50%) scale(0)}
/* Vertical slices */
.t-slices{display:flex;position:absolute;inset:0}
.t-slice{flex:1;background:var(--c,#38bdf8)}
/* ANIMATIONS triggered by .animating class */
/* 1 — horizontal wipe */
.anim-wipe.enter .t-wipe{animation:trn12-wipeEnter .5s cubic-bezier(.7,0,.2,1) forwards}
.anim-wipe.exit .t-wipe{animation:trn12-wipeExit .5s .4s cubic-bezier(.7,0,.2,1) forwards}
@keyframes trn12-wipeEnter{from{transform:translateX(-100%)}to{transform:translateX(0)}}
@keyframes trn12-wipeExit{from{transform:translateX(0)}to{transform:translateX(100%)}}
/* 2 — curtain */
.anim-curtain.enter .t-curtain-l{animation:trn12-curtLEnter .45s cubic-bezier(.7,0,.2,1) forwards}
.anim-curtain.enter .t-curtain-r{animation:trn12-curtREnter .45s .05s cubic-bezier(.7,0,.2,1) forwards}
.anim-curtain.exit .t-curtain-l{animation:trn12-curtLExit .45s .4s cubic-bezier(.7,0,.2,1) forwards}
.anim-curtain.exit .t-curtain-r{animation:trn12-curtRExit .45s .45s cubic-bezier(.7,0,.2,1) forwards}
@keyframes trn12-curtLEnter{from{transform:translateX(-100%)}to{transform:translateX(0)}}
@keyframes trn12-curtREnter{from{transform:translateX(100%)}to{transform:translateX(0)}}
@keyframes trn12-curtLExit{from{transform:translateX(0)}to{transform:translateX(-100%)}}
@keyframes trn12-curtRExit{from{transform:translateX(0)}to{transform:translateX(100%)}}
/* 3 — circle */
.anim-circle.enter .t-circle{animation:trn12-circleIn .5s cubic-bezier(.22,1,.36,1) forwards}
.anim-circle.exit .t-circle{animation:trn12-circleOut .5s .4s cubic-bezier(.7,0,.2,1) forwards;transform:translate(-50%,-50%) scale(3)}
@keyframes trn12-circleIn{from{transform:translate(-50%,-50%) scale(0)}to{transform:translate(-50%,-50%) scale(3)}}
@keyframes trn12-circleOut{from{transform:translate(-50%,-50%) scale(3)}to{transform:translate(-50%,-50%) scale(0)}}
/* 4 — slices stagger */
.anim-slices.enter .t-slice:nth-child(1){animation:trn12-sliceIn .4s cubic-bezier(.7,0,.2,1) forwards}
.anim-slices.enter .t-slice:nth-child(2){animation:trn12-sliceIn .4s .05s cubic-bezier(.7,0,.2,1) forwards}
.anim-slices.enter .t-slice:nth-child(3){animation:trn12-sliceIn .4s .1s cubic-bezier(.7,0,.2,1) forwards}
.anim-slices.enter .t-slice:nth-child(4){animation:trn12-sliceIn .4s .15s cubic-bezier(.7,0,.2,1) forwards}
.anim-slices.enter .t-slice:nth-child(5){animation:trn12-sliceIn .4s .2s cubic-bezier(.7,0,.2,1) forwards}
.anim-slices.exit .t-slice:nth-child(1){animation:trn12-sliceOut .4s .4s cubic-bezier(.7,0,.2,1) forwards}
.anim-slices.exit .t-slice:nth-child(2){animation:trn12-sliceOut .4s .45s cubic-bezier(.7,0,.2,1) forwards}
.anim-slices.exit .t-slice:nth-child(3){animation:trn12-sliceOut .4s .5s cubic-bezier(.7,0,.2,1) forwards}
.anim-slices.exit .t-slice:nth-child(4){animation:trn12-sliceOut .4s .55s cubic-bezier(.7,0,.2,1) forwards}
.anim-slices.exit .t-slice:nth-child(5){animation:trn12-sliceOut .4s .6s cubic-bezier(.7,0,.2,1) forwards}
@keyframes trn12-sliceIn{from{transform:scaleY(0);transform-origin:top}to{transform:scaleY(1)}}
@keyframes trn12-sliceOut{from{transform:scaleY(1);transform-origin:bottom}to{transform:scaleY(0)}}
/* transition type selector */
.type-select{position:fixed;top:20px;left:50%;transform:translateX(-50%);z-index:300;display:flex;gap:8px;background:rgba(255,255,255,.08);backdrop-filter:blur(12px);padding:8px;border-radius:30px;border:1px solid rgba(255,255,255,.12)}
.ts-btn{padding:8px 16px;border-radius:22px;border:none;background:transparent;color:rgba(255,255,255,.5);font-family:'Lexend';font-size:.78rem;font-weight:600;cursor:pointer;transition:background .2s,color .2s}
.ts-btn.active,.ts-btn:hover{background:rgba(255,255,255,.15);color:#fff}
/* page indicator */
.dots{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);z-index:300;display:flex;gap:8px}
.dot{width:8px;height:8px;border-radius:50%;background:rgba(255,255,255,.25);transition:background .3s,transform .3s;cursor:pointer}
.dot.active{background:#fff;transform:scale(1.3)}
@media (prefers-reduced-motion:reduce){.t-overlay *{animation:none !important}} @import url('https://fonts.googleapis.com/css2?family=Lexend:wght@300;400;600;700;800&display=swap');
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:'Lexend',sans-serif;background:#0d0d14;color:#f1f5f9;min-height:100vh;overflow:hidden}
/* page wrapper */
.page-app{width:100%;height:100vh;position:relative}
/* pages */
.page{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 24px;text-align:center;opacity:0;pointer-events:none;transition:opacity .01s 1s}
.page.active{opacity:1;pointer-events:auto;transition:opacity .01s}
.page h2{font-size:clamp(2.5rem,8vw,5rem);font-weight:800;margin-bottom:16px;letter-spacing:-.04em}
.page p{font-size:1rem;max-width:50ch;opacity:.6;line-height:1.6;margin-bottom:40px}
.page-num{font-size:.7rem;letter-spacing:.3em;text-transform:uppercase;opacity:.4;margin-bottom:16px}
.next-btn{padding:14px 36px;border-radius:30px;border:2px solid rgba(255,255,255,.2);background:transparent;color:#fff;font-family:'Lexend';font-weight:600;font-size:.9rem;cursor:pointer;transition:background .2s,border-color .2s}
.next-btn:hover{background:rgba(255,255,255,.1);border-color:rgba(255,255,255,.4)}
/* page backgrounds */
.pg1{background:linear-gradient(135deg,#0f0c29,#302b63,#24243e)}
.pg2{background:linear-gradient(135deg,#0a2e1a,#116545,#0d4a32)}
.pg3{background:linear-gradient(135deg,#2d0518,#7b0d2b,#4a0a1e)}
.pg4{background:linear-gradient(135deg,#0d1a3a,#1a3a6b,#0a2550)}
/* TRANSITION OVERLAY */
.t-overlay{position:fixed;inset:0;z-index:200;pointer-events:none}
/* Wipe variants */
.t-wipe{background:var(--c,#a78bfa)}
/* Curtain pair */
.t-curtain-l,.t-curtain-r{position:absolute;top:0;bottom:0;width:51%;background:var(--c,#f97316)}
.t-curtain-l{left:0}
.t-curtain-r{right:0}
/* Circle burst */
.t-circle{position:absolute;top:50%;left:50%;border-radius:50%;background:var(--c,#10b981);transform:translate(-50%,-50%) scale(0)}
/* Vertical slices */
.t-slices{display:flex;position:absolute;inset:0}
.t-slice{flex:1;background:var(--c,#38bdf8)}
/* ANIMATIONS triggered by .animating class */
/* 1 — horizontal wipe */
.anim-wipe.enter .t-wipe{animation:trn12-wipeEnter .5s cubic-bezier(.7,0,.2,1) forwards}
.anim-wipe.exit .t-wipe{animation:trn12-wipeExit .5s .4s cubic-bezier(.7,0,.2,1) forwards}
@keyframes trn12-wipeEnter{from{transform:translateX(-100%)}to{transform:translateX(0)}}
@keyframes trn12-wipeExit{from{transform:translateX(0)}to{transform:translateX(100%)}}
/* 2 — curtain */
.anim-curtain.enter .t-curtain-l{animation:trn12-curtLEnter .45s cubic-bezier(.7,0,.2,1) forwards}
.anim-curtain.enter .t-curtain-r{animation:trn12-curtREnter .45s .05s cubic-bezier(.7,0,.2,1) forwards}
.anim-curtain.exit .t-curtain-l{animation:trn12-curtLExit .45s .4s cubic-bezier(.7,0,.2,1) forwards}
.anim-curtain.exit .t-curtain-r{animation:trn12-curtRExit .45s .45s cubic-bezier(.7,0,.2,1) forwards}
@keyframes trn12-curtLEnter{from{transform:translateX(-100%)}to{transform:translateX(0)}}
@keyframes trn12-curtREnter{from{transform:translateX(100%)}to{transform:translateX(0)}}
@keyframes trn12-curtLExit{from{transform:translateX(0)}to{transform:translateX(-100%)}}
@keyframes trn12-curtRExit{from{transform:translateX(0)}to{transform:translateX(100%)}}
/* 3 — circle */
.anim-circle.enter .t-circle{animation:trn12-circleIn .5s cubic-bezier(.22,1,.36,1) forwards}
.anim-circle.exit .t-circle{animation:trn12-circleOut .5s .4s cubic-bezier(.7,0,.2,1) forwards;transform:translate(-50%,-50%) scale(3)}
@keyframes trn12-circleIn{from{transform:translate(-50%,-50%) scale(0)}to{transform:translate(-50%,-50%) scale(3)}}
@keyframes trn12-circleOut{from{transform:translate(-50%,-50%) scale(3)}to{transform:translate(-50%,-50%) scale(0)}}
/* 4 — slices stagger */
.anim-slices.enter .t-slice:nth-child(1){animation:trn12-sliceIn .4s cubic-bezier(.7,0,.2,1) forwards}
.anim-slices.enter .t-slice:nth-child(2){animation:trn12-sliceIn .4s .05s cubic-bezier(.7,0,.2,1) forwards}
.anim-slices.enter .t-slice:nth-child(3){animation:trn12-sliceIn .4s .1s cubic-bezier(.7,0,.2,1) forwards}
.anim-slices.enter .t-slice:nth-child(4){animation:trn12-sliceIn .4s .15s cubic-bezier(.7,0,.2,1) forwards}
.anim-slices.enter .t-slice:nth-child(5){animation:trn12-sliceIn .4s .2s cubic-bezier(.7,0,.2,1) forwards}
.anim-slices.exit .t-slice:nth-child(1){animation:trn12-sliceOut .4s .4s cubic-bezier(.7,0,.2,1) forwards}
.anim-slices.exit .t-slice:nth-child(2){animation:trn12-sliceOut .4s .45s cubic-bezier(.7,0,.2,1) forwards}
.anim-slices.exit .t-slice:nth-child(3){animation:trn12-sliceOut .4s .5s cubic-bezier(.7,0,.2,1) forwards}
.anim-slices.exit .t-slice:nth-child(4){animation:trn12-sliceOut .4s .55s cubic-bezier(.7,0,.2,1) forwards}
.anim-slices.exit .t-slice:nth-child(5){animation:trn12-sliceOut .4s .6s cubic-bezier(.7,0,.2,1) forwards}
@keyframes trn12-sliceIn{from{transform:scaleY(0);transform-origin:top}to{transform:scaleY(1)}}
@keyframes trn12-sliceOut{from{transform:scaleY(1);transform-origin:bottom}to{transform:scaleY(0)}}
/* transition type selector */
.type-select{position:fixed;top:20px;left:50%;transform:translateX(-50%);z-index:300;display:flex;gap:8px;background:rgba(255,255,255,.08);backdrop-filter:blur(12px);padding:8px;border-radius:30px;border:1px solid rgba(255,255,255,.12)}
.ts-btn{padding:8px 16px;border-radius:22px;border:none;background:transparent;color:rgba(255,255,255,.5);font-family:'Lexend';font-size:.78rem;font-weight:600;cursor:pointer;transition:background .2s,color .2s}
.ts-btn.active,.ts-btn:hover{background:rgba(255,255,255,.15);color:#fff}
/* page indicator */
.dots{position:fixed;bottom:24px;left:50%;transform:translateX(-50%);z-index:300;display:flex;gap:8px}
.dot{width:8px;height:8px;border-radius:50%;background:rgba(255,255,255,.25);transition:background .3s,transform .3s;cursor:pointer}
.dot.active{background:#fff;transform:scale(1.3)}
@media (prefers-reduced-motion:reduce){.t-overlay *{animation:none !important}}let currentPage = 0;
let currentType = 'wipe';
let animating = false;
const pages = document.querySelectorAll('.page');
const overlay = document.getElementById('overlay');
const dotsEl = document.getElementById('dots');
// Build dots
pages.forEach((_,i) => {
const d = document.createElement('div');
d.className = 'dot' + (i===0?' active':'');
d.onclick = () => goToPage(i);
dotsEl.appendChild(d);
});
// Type selector
document.getElementById('typeSelect').addEventListener('click', e => {
const btn = e.target.closest('[data-type]');
if(!btn) return;
currentType = btn.dataset.type;
document.querySelectorAll('.ts-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
});
function nextPage(){ goToPage((currentPage+1) % pages.length); }
function goToPage(next){
if(animating || next === currentPage) return;
animating = true;
overlay.className = 't-overlay anim-' + currentType + ' enter';
setTimeout(() => {
pages[currentPage].classList.remove('active');
currentPage = next;
pages[currentPage].classList.add('active');
document.querySelectorAll('.dot').forEach((d,i) => d.classList.toggle('active', i===currentPage));
overlay.classList.remove('enter');
overlay.classList.add('exit');
setTimeout(() => { overlay.className = 't-overlay'; animating = false; }, 1000);
}, 480);
} let currentPage = 0;
let currentType = 'wipe';
let animating = false;
const pages = document.querySelectorAll('.page');
const overlay = document.getElementById('overlay');
const dotsEl = document.getElementById('dots');
// Build dots
pages.forEach((_,i) => {
const d = document.createElement('div');
d.className = 'dot' + (i===0?' active':'');
d.onclick = () => goToPage(i);
dotsEl.appendChild(d);
});
// Type selector
document.getElementById('typeSelect').addEventListener('click', e => {
const btn = e.target.closest('[data-type]');
if(!btn) return;
currentType = btn.dataset.type;
document.querySelectorAll('.ts-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
});
function nextPage(){ goToPage((currentPage+1) % pages.length); }
function goToPage(next){
if(animating || next === currentPage) return;
animating = true;
overlay.className = 't-overlay anim-' + currentType + ' enter';
setTimeout(() => {
pages[currentPage].classList.remove('active');
currentPage = next;
pages[currentPage].classList.add('active');
document.querySelectorAll('.dot').forEach((d,i) => d.classList.toggle('active', i===currentPage));
overlay.classList.remove('enter');
overlay.classList.add('exit');
setTimeout(() => { overlay.className = 't-overlay'; animating = false; }, 1000);
}, 480);
}More from 22 CSS Transition Effects
Text Reveal AnimationImage Zoom Hover TransitionBackground Color TransitionBorder Animation TransitionNavigation Hover TransitionUnderline Animation HoverGlassmorphism Hover TransitionSlide-In Animation on ScrollFade In Fade Out TransitionLoading Skeleton TransitionModal Open Close TransitionRipple Effect on Click
View the full collection →