Patterns Easy
Blur Fade
Elements that fade in from blurred to sharp as they enter the viewport on scroll, creating a smooth reveal effect.
Open in Lab
MCP
css javascript vue svelte
Targets: TS JS HTML React Vue Svelte
Code
*,
*::before,
*::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
:root {
--blur-amount: 10px;
--blur-duration: 0.8s;
--blur-ease: cubic-bezier(0.16, 1, 0.3, 1);
--accent: #818cf8;
--accent-dim: rgba(99, 102, 241, 0.15);
}
body {
font-family: system-ui, -apple-system, sans-serif;
background: #0a0a0a;
color: #e2e8f0;
min-height: 300vh;
overflow-x: hidden;
}
.hero {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 1rem;
text-align: center;
padding: 4rem 2rem 2rem;
}
.hero h1 {
font-size: clamp(2rem, 5vw, 3.5rem);
font-weight: 800;
letter-spacing: -0.03em;
background: linear-gradient(135deg, #e0e7ff 0%, #818cf8 50%, #6366f1 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero p {
color: rgba(148, 163, 184, 0.8);
font-size: 1.125rem;
}
.scroll-hint {
margin-top: 2rem;
color: rgba(148, 163, 184, 0.5);
font-size: 0.875rem;
animation: bounce 2s ease-in-out infinite;
}
@keyframes bounce {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(8px);
}
}
.content-section {
max-width: 900px;
margin: 0 auto;
padding: 4rem 2rem;
display: flex;
flex-direction: column;
gap: 3rem;
}
/* Blur-fade initial state */
.blur-fade {
filter: blur(var(--blur-amount));
opacity: 0;
transform: translateY(20px);
transition: filter var(--blur-duration) var(--blur-ease), opacity var(--blur-duration)
var(--blur-ease), transform var(--blur-duration) var(--blur-ease);
will-change: filter, opacity, transform;
}
/* Animated (visible) state */
.blur-fade.is-visible {
filter: blur(0);
opacity: 1;
transform: translateY(0);
}
/* Demo cards */
.card {
background: rgba(255, 255, 255, 0.04);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 16px;
padding: 2rem;
backdrop-filter: blur(8px);
}
.card h3 {
font-size: 1.25rem;
font-weight: 700;
margin-bottom: 0.75rem;
color: #f1f5f9;
}
.card p {
color: rgba(148, 163, 184, 0.8);
line-height: 1.7;
font-size: 0.95rem;
}
.card-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
gap: 1.5rem;
}
.card-grid .card {
border-top: 2px solid var(--accent-dim);
}
.card .icon {
width: 48px;
height: 48px;
border-radius: 12px;
background: var(--accent-dim);
display: grid;
place-items: center;
margin-bottom: 1rem;
font-size: 1.5rem;
}
.section-title {
font-size: 1.75rem;
font-weight: 700;
color: #f1f5f9;
}
.section-text {
color: rgba(148, 163, 184, 0.8);
line-height: 1.8;
font-size: 1rem;
max-width: 640px;
}// Blur Fade — IntersectionObserver-based scroll reveal with blur transition
(function () {
"use strict";
const DEFAULTS = {
threshold: 0.15,
rootMargin: "0px 0px -40px 0px",
staggerDelay: 120,
};
function initBlurFade() {
const elements = document.querySelectorAll(".blur-fade");
if (!elements.length) return;
// Group elements by their parent to enable stagger
const groups = new Map();
elements.forEach((el) => {
const parent = el.parentElement;
if (!groups.has(parent)) {
groups.set(parent, []);
}
groups.get(parent).push(el);
});
// Assign stagger delays within each group
groups.forEach((children) => {
children.forEach((el, i) => {
const customDelay = el.dataset.delay;
const delay = customDelay ? parseInt(customDelay, 10) : i * DEFAULTS.staggerDelay;
el.style.transitionDelay = `${delay}ms`;
});
});
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add("is-visible");
observer.unobserve(entry.target);
}
});
},
{
threshold: DEFAULTS.threshold,
rootMargin: DEFAULTS.rootMargin,
}
);
elements.forEach((el) => observer.observe(el));
}
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", initBlurFade);
} else {
initBlurFade();
}
})();<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Blur Fade</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<section class="hero">
<h1>Blur Fade</h1>
<p>Elements fade from blurred to sharp as you scroll</p>
<span class="scroll-hint">Scroll down to see the effect</span>
</section>
<section class="content-section">
<div class="blur-fade">
<h2 class="section-title">Smooth Reveal</h2>
</div>
<div class="blur-fade">
<p class="section-text">
Content emerges from a soft blur as it enters the viewport, creating
a polished and modern scroll experience. Each element transitions
independently with staggered timing.
</p>
</div>
<div class="card-grid">
<div class="blur-fade card">
<div class="icon">◈</div>
<h3>Performance</h3>
<p>Hardware-accelerated CSS transitions keep animations silky smooth at 60fps.</p>
</div>
<div class="blur-fade card">
<div class="icon">◎</div>
<h3>Accessible</h3>
<p>Uses IntersectionObserver for efficient, non-blocking scroll detection.</p>
</div>
<div class="blur-fade card">
<div class="icon">✦</div>
<h3>Customizable</h3>
<p>Control blur amount, duration, easing, and stagger timing via CSS variables.</p>
</div>
</div>
<div class="blur-fade">
<h2 class="section-title">Cascading Effect</h2>
</div>
<div class="blur-fade">
<p class="section-text">
Elements within the same parent container automatically receive staggered
delays, creating a beautiful cascading reveal without any extra configuration.
</p>
</div>
<div class="card-grid">
<div class="blur-fade card">
<div class="icon">★</div>
<h3>Zero Dependencies</h3>
<p>Pure CSS and vanilla JavaScript. No libraries required.</p>
</div>
<div class="blur-fade card">
<div class="icon">⚙</div>
<h3>Easy Setup</h3>
<p>Just add the blur-fade class to any element you want to reveal.</p>
</div>
<div class="blur-fade card">
<div class="icon">✨</div>
<h3>One-time Trigger</h3>
<p>Elements are unobserved after reveal, keeping the observer lightweight.</p>
</div>
</div>
</section>
<script src="script.js"></script>
</body>
</html>import { useEffect, useRef, useState, type CSSProperties, type ReactNode } from "react";
interface BlurFadeProps {
children: ReactNode;
blurAmount?: number;
duration?: number;
delay?: number;
yOffset?: number;
threshold?: number;
className?: string;
style?: CSSProperties;
}
export function BlurFade({
children,
blurAmount = 10,
duration = 800,
delay = 0,
yOffset = 20,
threshold = 0.15,
className = "",
style = {},
}: BlurFadeProps) {
const ref = useRef<HTMLDivElement>(null);
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const el = ref.current;
if (!el) return;
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setIsVisible(true);
observer.unobserve(el);
}
},
{ threshold, rootMargin: "0px 0px -40px 0px" }
);
observer.observe(el);
return () => observer.disconnect();
}, [threshold]);
const baseStyle: CSSProperties = {
filter: isVisible ? "blur(0px)" : `blur(${blurAmount}px)`,
opacity: isVisible ? 1 : 0,
transform: isVisible ? "translateY(0)" : `translateY(${yOffset}px)`,
transition: `filter ${duration}ms cubic-bezier(0.16, 1, 0.3, 1) ${delay}ms, opacity ${duration}ms cubic-bezier(0.16, 1, 0.3, 1) ${delay}ms, transform ${duration}ms cubic-bezier(0.16, 1, 0.3, 1) ${delay}ms`,
willChange: "filter, opacity, transform",
...style,
};
return (
<div ref={ref} className={className} style={baseStyle}>
{children}
</div>
);
}
// Demo usage
export default function BlurFadeDemo() {
const cards = [
{
icon: "\u25C8",
title: "Performance",
desc: "Hardware-accelerated CSS transitions keep animations silky smooth at 60fps.",
},
{
icon: "\u25CE",
title: "Accessible",
desc: "Uses IntersectionObserver for efficient, non-blocking scroll detection.",
},
{
icon: "\u2726",
title: "Customizable",
desc: "Control blur amount, duration, easing, and stagger timing via props.",
},
{
icon: "\u2605",
title: "Zero Dependencies",
desc: "Pure React with no external libraries required.",
},
{
icon: "\u2699",
title: "Easy Setup",
desc: "Just wrap any content in the BlurFade component.",
},
{
icon: "\u2728",
title: "One-time Trigger",
desc: "Elements are unobserved after reveal, keeping things lightweight.",
},
];
return (
<div
style={{
background: "#0a0a0a",
minHeight: "300vh",
fontFamily: "system-ui, -apple-system, sans-serif",
color: "#e2e8f0",
}}
>
<section
style={{
height: "100vh",
display: "flex",
flexDirection: "column",
alignItems: "center",
justifyContent: "center",
gap: "1rem",
textAlign: "center",
padding: "2rem",
}}
>
<h1
style={{
fontSize: "clamp(2rem, 5vw, 3.5rem)",
fontWeight: 800,
letterSpacing: "-0.03em",
background: "linear-gradient(135deg, #e0e7ff 0%, #818cf8 50%, #6366f1 100%)",
WebkitBackgroundClip: "text",
WebkitTextFillColor: "transparent",
backgroundClip: "text",
}}
>
Blur Fade
</h1>
<p style={{ color: "rgba(148, 163, 184, 0.8)", fontSize: "1.125rem" }}>
Elements fade from blurred to sharp as you scroll
</p>
<span
style={{ marginTop: "2rem", color: "rgba(148, 163, 184, 0.5)", fontSize: "0.875rem" }}
>
Scroll down to see the effect
</span>
</section>
<section
style={{
maxWidth: 900,
margin: "0 auto",
padding: "4rem 2rem",
display: "flex",
flexDirection: "column",
gap: "3rem",
}}
>
<BlurFade>
<h2 style={{ fontSize: "1.75rem", fontWeight: 700, color: "#f1f5f9" }}>Smooth Reveal</h2>
</BlurFade>
<BlurFade delay={100}>
<p style={{ color: "rgba(148, 163, 184, 0.8)", lineHeight: 1.8, maxWidth: 640 }}>
Content emerges from a soft blur as it enters the viewport, creating a polished and
modern scroll experience.
</p>
</BlurFade>
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(260px, 1fr))",
gap: "1.5rem",
}}
>
{cards.slice(0, 3).map((card, i) => (
<BlurFade key={card.title} delay={i * 120}>
<div
style={{
background: "rgba(255,255,255,0.04)",
border: "1px solid rgba(255,255,255,0.08)",
borderTop: "2px solid rgba(99,102,241,0.15)",
borderRadius: 16,
padding: "2rem",
}}
>
<div
style={{
width: 48,
height: 48,
borderRadius: 12,
background: "rgba(99,102,241,0.15)",
display: "grid",
placeItems: "center",
marginBottom: "1rem",
fontSize: "1.5rem",
}}
>
{card.icon}
</div>
<h3
style={{
fontSize: "1.25rem",
fontWeight: 700,
marginBottom: "0.75rem",
color: "#f1f5f9",
}}
>
{card.title}
</h3>
<p style={{ color: "rgba(148,163,184,0.8)", lineHeight: 1.7, fontSize: "0.95rem" }}>
{card.desc}
</p>
</div>
</BlurFade>
))}
</div>
<BlurFade>
<h2 style={{ fontSize: "1.75rem", fontWeight: 700, color: "#f1f5f9" }}>
Cascading Effect
</h2>
</BlurFade>
<div
style={{
display: "grid",
gridTemplateColumns: "repeat(auto-fit, minmax(260px, 1fr))",
gap: "1.5rem",
}}
>
{cards.slice(3).map((card, i) => (
<BlurFade key={card.title} delay={i * 120}>
<div
style={{
background: "rgba(255,255,255,0.04)",
border: "1px solid rgba(255,255,255,0.08)",
borderTop: "2px solid rgba(99,102,241,0.15)",
borderRadius: 16,
padding: "2rem",
}}
>
<div
style={{
width: 48,
height: 48,
borderRadius: 12,
background: "rgba(99,102,241,0.15)",
display: "grid",
placeItems: "center",
marginBottom: "1rem",
fontSize: "1.5rem",
}}
>
{card.icon}
</div>
<h3
style={{
fontSize: "1.25rem",
fontWeight: 700,
marginBottom: "0.75rem",
color: "#f1f5f9",
}}
>
{card.title}
</h3>
<p style={{ color: "rgba(148,163,184,0.8)", lineHeight: 1.7, fontSize: "0.95rem" }}>
{card.desc}
</p>
</div>
</BlurFade>
))}
</div>
</section>
</div>
);
}<script setup>
import { ref, onMounted, onUnmounted } from "vue";
const cards = [
{
icon: "◈",
title: "Performance",
desc: "Hardware-accelerated CSS transitions keep animations silky smooth at 60fps.",
},
{
icon: "◎",
title: "Accessible",
desc: "Uses IntersectionObserver for efficient, non-blocking scroll detection.",
},
{
icon: "✦",
title: "Customizable",
desc: "Control blur amount, duration, easing, and stagger timing via props.",
},
{ icon: "★", title: "Zero Dependencies", desc: "Pure Vue with no external libraries required." },
{ icon: "⚙", title: "Easy Setup", desc: "Just use the v-blur-fade directive on any element." },
{
icon: "✨",
title: "One-time Trigger",
desc: "Elements are unobserved after reveal, keeping things lightweight.",
},
];
const vBlurFade = {
mounted(el, binding) {
const delay = binding.value?.delay ?? 0;
const threshold = binding.value?.threshold ?? 0.15;
el.style.filter = "blur(10px)";
el.style.opacity = "0";
el.style.transform = "translateY(20px)";
el.style.transition = `filter 800ms cubic-bezier(0.16,1,0.3,1) ${delay}ms, opacity 800ms cubic-bezier(0.16,1,0.3,1) ${delay}ms, transform 800ms cubic-bezier(0.16,1,0.3,1) ${delay}ms`;
el.style.willChange = "filter, opacity, transform";
const obs = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
el.style.filter = "blur(0px)";
el.style.opacity = "1";
el.style.transform = "translateY(0)";
obs.unobserve(el);
}
},
{ threshold, rootMargin: "0px 0px -40px 0px" }
);
obs.observe(el);
el._blurObs = obs;
},
unmounted(el) {
el._blurObs?.disconnect();
},
};
</script>
<template>
<div style="background:#0a0a0a;min-height:300vh;font-family:system-ui,-apple-system,sans-serif;color:#e2e8f0">
<section style="height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1rem;text-align:center;padding:2rem">
<h1 style="font-size:clamp(2rem,5vw,3.5rem);font-weight:800;letter-spacing:-0.03em;background:linear-gradient(135deg,#e0e7ff 0%,#818cf8 50%,#6366f1 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text">Blur Fade</h1>
<p style="color:rgba(148,163,184,0.8);font-size:1.125rem">Elements fade from blurred to sharp as you scroll</p>
<span style="margin-top:2rem;color:rgba(148,163,184,0.5);font-size:0.875rem">Scroll down to see the effect</span>
</section>
<section style="max-width:900px;margin:0 auto;padding:4rem 2rem;display:flex;flex-direction:column;gap:3rem">
<h2 v-blur-fade style="font-size:1.75rem;font-weight:700;color:#f1f5f9">Smooth Reveal</h2>
<p v-blur-fade="{ delay: 100 }" style="color:rgba(148,163,184,0.8);line-height:1.8;max-width:640px">Content emerges from a soft blur as it enters the viewport.</p>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:1.5rem">
<div v-for="(card, i) in cards.slice(0,3)" :key="card.title" v-blur-fade="{ delay: i * 120 }" style="background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);border-top:2px solid rgba(99,102,241,0.15);border-radius:16px;padding:2rem">
<div style="width:48px;height:48px;border-radius:12px;background:rgba(99,102,241,0.15);display:grid;place-items:center;margin-bottom:1rem;font-size:1.5rem">{{ card.icon }}</div>
<h3 style="font-size:1.25rem;font-weight:700;margin-bottom:0.75rem;color:#f1f5f9">{{ card.title }}</h3>
<p style="color:rgba(148,163,184,0.8);line-height:1.7;font-size:0.95rem">{{ card.desc }}</p>
</div>
</div>
<h2 v-blur-fade style="font-size:1.75rem;font-weight:700;color:#f1f5f9">Cascading Effect</h2>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:1.5rem">
<div v-for="(card, i) in cards.slice(3)" :key="card.title" v-blur-fade="{ delay: i * 120 }" style="background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);border-top:2px solid rgba(99,102,241,0.15);border-radius:16px;padding:2rem">
<div style="width:48px;height:48px;border-radius:12px;background:rgba(99,102,241,0.15);display:grid;place-items:center;margin-bottom:1rem;font-size:1.5rem">{{ card.icon }}</div>
<h3 style="font-size:1.25rem;font-weight:700;margin-bottom:0.75rem;color:#f1f5f9">{{ card.title }}</h3>
<p style="color:rgba(148,163,184,0.8);line-height:1.7;font-size:0.95rem">{{ card.desc }}</p>
</div>
</div>
</section>
</div>
</template><script>
import { onMount } from "svelte";
const cards = [
{
icon: "◈",
title: "Performance",
desc: "Hardware-accelerated CSS transitions keep animations silky smooth at 60fps.",
},
{
icon: "◎",
title: "Accessible",
desc: "Uses IntersectionObserver for efficient, non-blocking scroll detection.",
},
{
icon: "✦",
title: "Customizable",
desc: "Control blur amount, duration, easing, and stagger timing via props.",
},
{
icon: "★",
title: "Zero Dependencies",
desc: "Pure Svelte with no external libraries required.",
},
{ icon: "⚙", title: "Easy Setup", desc: "Just use the blur-fade action on any element." },
{
icon: "✨",
title: "One-time Trigger",
desc: "Elements are unobserved after reveal, keeping things lightweight.",
},
];
function blurFade(node, { delay = 0, threshold = 0.15 } = {}) {
node.style.filter = "blur(10px)";
node.style.opacity = "0";
node.style.transform = "translateY(20px)";
node.style.transition = `filter 800ms cubic-bezier(0.16,1,0.3,1) ${delay}ms, opacity 800ms cubic-bezier(0.16,1,0.3,1) ${delay}ms, transform 800ms cubic-bezier(0.16,1,0.3,1) ${delay}ms`;
node.style.willChange = "filter, opacity, transform";
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
node.style.filter = "blur(0px)";
node.style.opacity = "1";
node.style.transform = "translateY(0)";
observer.unobserve(node);
}
},
{ threshold, rootMargin: "0px 0px -40px 0px" }
);
observer.observe(node);
return {
destroy() {
observer.disconnect();
},
};
}
</script>
<div style="background:#0a0a0a;min-height:300vh;font-family:system-ui,-apple-system,sans-serif;color:#e2e8f0">
<section style="height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1rem;text-align:center;padding:2rem">
<h1 style="font-size:clamp(2rem,5vw,3.5rem);font-weight:800;letter-spacing:-0.03em;background:linear-gradient(135deg,#e0e7ff 0%,#818cf8 50%,#6366f1 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text">Blur Fade</h1>
<p style="color:rgba(148,163,184,0.8);font-size:1.125rem">Elements fade from blurred to sharp as you scroll</p>
<span style="margin-top:2rem;color:rgba(148,163,184,0.5);font-size:0.875rem">Scroll down to see the effect</span>
</section>
<section style="max-width:900px;margin:0 auto;padding:4rem 2rem;display:flex;flex-direction:column;gap:3rem">
<h2 use:blurFade style="font-size:1.75rem;font-weight:700;color:#f1f5f9">Smooth Reveal</h2>
<p use:blurFade={{ delay: 100 }} style="color:rgba(148,163,184,0.8);line-height:1.8;max-width:640px">Content emerges from a soft blur as it enters the viewport, creating a polished and modern scroll experience.</p>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:1.5rem">
{#each cards.slice(0,3) as card, i}
<div use:blurFade={{ delay: i * 120 }} style="background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);border-top:2px solid rgba(99,102,241,0.15);border-radius:16px;padding:2rem">
<div style="width:48px;height:48px;border-radius:12px;background:rgba(99,102,241,0.15);display:grid;place-items:center;margin-bottom:1rem;font-size:1.5rem">{card.icon}</div>
<h3 style="font-size:1.25rem;font-weight:700;margin-bottom:0.75rem;color:#f1f5f9">{card.title}</h3>
<p style="color:rgba(148,163,184,0.8);line-height:1.7;font-size:0.95rem">{card.desc}</p>
</div>
{/each}
</div>
<h2 use:blurFade style="font-size:1.75rem;font-weight:700;color:#f1f5f9">Cascading Effect</h2>
<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:1.5rem">
{#each cards.slice(3) as card, i}
<div use:blurFade={{ delay: i * 120 }} style="background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);border-top:2px solid rgba(99,102,241,0.15);border-radius:16px;padding:2rem">
<div style="width:48px;height:48px;border-radius:12px;background:rgba(99,102,241,0.15);display:grid;place-items:center;margin-bottom:1rem;font-size:1.5rem">{card.icon}</div>
<h3 style="font-size:1.25rem;font-weight:700;margin-bottom:0.75rem;color:#f1f5f9">{card.title}</h3>
<p style="color:rgba(148,163,184,0.8);line-height:1.7;font-size:0.95rem">{card.desc}</p>
</div>
{/each}
</div>
</section>
</div>Blur Fade
A smooth blur-to-sharp fade-in effect triggered as elements scroll into the viewport. Elements start blurred and transparent, then animate to full clarity.
How it works
- Elements begin with
filter: blur(10px)andopacity: 0 - An IntersectionObserver watches for elements entering the viewport
- When visible, elements transition to
blur(0)andopacity: 1 - Staggered delays create a cascading reveal effect
Customization
- Adjust
--blur-amountto control the initial blur intensity - Modify
--blur-durationto change the transition speed - Add
data-delayattributes for custom stagger timing - Control the trigger threshold via the IntersectionObserver options
When to use it
- Landing page content sections
- Card grids and feature lists
- Portfolio galleries
- Any content that benefits from a progressive reveal on scroll