*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background: #050910;
color: #f2f6ff;
}
.hero {
height: 100vh;
display: grid;
place-items: center;
place-content: center;
gap: 1rem;
text-align: center;
}
.hero-label {
color: #334155;
font-size: 0.875rem;
letter-spacing: 0.1em;
text-transform: uppercase;
}
.hero-arrow {
font-size: 1.5rem;
color: #334155;
animation: bounce 1.5s ease-in-out infinite;
}
@keyframes bounce {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(8px);
}
}
/* โโ Sections โโ */
.reveal-section {
min-height: 100vh;
display: grid;
place-items: center;
padding: 4rem 2rem;
}
.reveal-block {
width: min(700px, 100%);
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.07);
border-radius: 1.5rem;
overflow: hidden;
}
.reveal-inner {
padding: 3rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.reveal-tag {
font-size: 0.72rem;
font-weight: 700;
letter-spacing: 0.1em;
text-transform: uppercase;
color: #38bdf8;
background: rgba(56, 189, 248, 0.1);
padding: 0.25rem 0.75rem;
border-radius: 999px;
align-self: flex-start;
}
.reveal-inner h2 {
font-size: clamp(2rem, 5vw, 3.5rem);
font-weight: 800;
letter-spacing: -0.03em;
line-height: 1.1;
}
.reveal-inner p {
font-size: 0.9rem;
color: #475569;
line-height: 1.65;
}
/* โโ Split variant โโ */
.reveal-split {
display: flex;
width: min(700px, 100%);
gap: 2px;
border-radius: 1.5rem;
overflow: hidden;
}
.reveal-half {
flex: 1;
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.07);
}
.reveal-half .reveal-inner {
padding: 3rem 2rem;
align-items: center;
text-align: center;
}
.reveal-half--left h2 {
color: #38bdf8;
font-size: clamp(2.5rem, 6vw, 5rem);
}
.reveal-half--right h2 {
color: #818cf8;
font-size: clamp(2.5rem, 6vw, 5rem);
}
(function () {
if (!window.gsap || !window.ScrollTrigger) return;
gsap.registerPlugin(ScrollTrigger);
const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
if (reduced) {
gsap.set(".reveal-block, .reveal-half", { clipPath: "inset(0 0% 0 0)" });
return;
}
// โโ Wipe from right โ left โโ
const leftEl = document.querySelector(".reveal--left");
if (leftEl) {
gsap.fromTo(
leftEl,
{ clipPath: "inset(0 100% 0 0)" },
{
clipPath: "inset(0 0% 0 0)",
ease: "power2.inOut",
scrollTrigger: { trigger: leftEl, start: "top 80%", end: "top 30%", scrub: 1 },
}
);
}
// โโ Wipe from bottom โ up โโ
const bottomEl = document.querySelector(".reveal--bottom");
if (bottomEl) {
gsap.fromTo(
bottomEl,
{ clipPath: "inset(100% 0 0 0)" },
{
clipPath: "inset(0% 0 0 0)",
ease: "power2.inOut",
scrollTrigger: { trigger: bottomEl, start: "top 80%", end: "top 20%", scrub: 1.2 },
}
);
}
// โโ Split reveal โ both halves from center out โโ
const leftHalf = document.querySelector(".reveal-half--left");
const rightHalf = document.querySelector(".reveal-half--right");
if (leftHalf && rightHalf) {
const tl = gsap.timeline({
scrollTrigger: {
trigger: leftHalf.closest(".reveal-split"),
start: "top 75%",
end: "top 25%",
scrub: 1,
},
});
tl.fromTo(
leftHalf,
{ clipPath: "inset(0 100% 0 0)" },
{ clipPath: "inset(0 0% 0 0)", ease: "power2.inOut" },
0
).fromTo(
rightHalf,
{ clipPath: "inset(0 0 0 100%)" },
{ clipPath: "inset(0 0 0 0%)", ease: "power2.inOut" },
0
);
}
// โโ Diagonal โ polygon from corner โโ
const diagEl = document.querySelector(".reveal--diagonal");
if (diagEl) {
gsap.fromTo(
diagEl,
{ clipPath: "polygon(0 0, 0 0, 0 100%, 0 100%)" },
{
clipPath: "polygon(0 0, 100% 0, 100% 100%, 0 100%)",
ease: "power3.inOut",
scrollTrigger: { trigger: diagEl, start: "top 78%", end: "top 20%", scrub: 1.5 },
}
);
}
})();
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Clip-path Reveal</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="hero">
<p class="hero-label">Scroll to reveal</p>
<div class="hero-arrow">โ</div>
</div>
<!-- Wipe from left -->
<section class="reveal-section">
<div class="reveal-block reveal--left">
<div class="reveal-inner">
<span class="reveal-tag">Wipe left</span>
<h2>Reveal from<br>the right edge</h2>
<p>Content sweeps in as the clip-path inset collapses.</p>
</div>
</div>
</section>
<!-- Wipe from bottom -->
<section class="reveal-section">
<div class="reveal-block reveal--bottom">
<div class="reveal-inner">
<span class="reveal-tag">Wipe up</span>
<h2>Content rises<br>from below</h2>
<p>The bottom inset animates from 100% to 0% on scrub.</p>
</div>
</div>
</section>
<!-- Split reveal -->
<section class="reveal-section">
<div class="reveal-split">
<div class="reveal-half reveal-half--left">
<div class="reveal-inner">
<h2>Split</h2>
</div>
</div>
<div class="reveal-half reveal-half--right">
<div class="reveal-inner">
<h2>Reveal</h2>
</div>
</div>
</div>
</section>
<!-- Diagonal reveal -->
<section class="reveal-section">
<div class="reveal-block reveal--diagonal">
<div class="reveal-inner">
<span class="reveal-tag">Diagonal</span>
<h2>Polygon clip<br>from corner</h2>
<p>clip-path: polygon() animates from a triangle to full rect.</p>
</div>
</div>
</section>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/gsap.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.12.5/dist/ScrollTrigger.min.js"></script>
<script src="script.js"></script>
</body>
</html>