BlurReveal: Smooth Fade and Blur Animation for Vue & Nuxt
A component that introduces a soft, cinematic reveal motion as elements scroll into view. Inspired by the natural rhythm of great animations.
Rahul Vashishtha
People notice when motion feels right.
It’s not about flash or speed — it’s about rhythm, timing, and intention.
When an element blurs into focus or slides into view gently, it communicates care. It tells the user: this interface was designed, not just assembled.
That’s the feeling I wanted to capture with BlurReveal —
a small Vue component that makes content appear gracefully as it scrolls into view.
Not instantly. Not abruptly. Just... naturally.
Why BlurReveal exists
Modern web interfaces often appear suddenly — content pops into place the moment it’s visible.
It’s functional, but it feels abrupt. Nothing in the physical world behaves like that.
BlurReveal slows things down just enough to make transitions feel organic.
Each element softly fades in, moves upward, and clears its blur — creating a sense of emergence, not appearance.
It’s a subtle difference, but one that changes how a page feels.
When to use it
Use BlurReveal when you want motion that supports the experience — not distracts from it.
- Sections that reveal as you scroll
- Grids of cards or images
- Feature highlights or testimonials
Avoid it for elements that are always visible, like navigation bars or headers.
Great motion has rhythm — it needs moments of rest.
How it’s used
<template>
<BlurReveal class="grid gap-8">
<FeatureCard />
<FeatureCard />
<FeatureCard />
</BlurReveal>
</template>
<script setup lang="ts">
import BlurReveal from "@/components/BlurReveal.vue";
import FeatureCard from "@/components/FeatureCard.vue";
</script>
That’s all you need. Each child fades in one by one as it scrolls into view — no extra setup, no manual triggers.
How it works
Under the hood, BlurReveal uses motion-v —
a lightweight motion library inspired by Framer Motion.
It observes when elements enter the viewport and animates them in sequence.
Here’s the idea:
- Capture every child passed into the slot.
- Wrap each in a
<Motion>component. - Define an initial state — blurred, invisible, and slightly lower.
- Animate to the final state — sharp, visible, and aligned.
- Stagger each item slightly to create rhythm.
This rhythm — the slight delay between elements — is what makes the reveal feel composed rather than mechanical.
Code overview
Props and defaults
interface Props {
duration?: number;
delay?: number;
blur?: string;
yOffset?: number;
class?: string;
}
Defaults:
duration: 1,
delay: 2,
blur: "20px",
yOffset: 20,
The delay multiplies with each item’s index — so if delay is 0.2,
the second element starts after 0.2s, the third after 0.4s, and so on.
A small adjustment that transforms static content into a flowing sequence.
Animation logic
function getInitial() {
return {
opacity: 0,
filter: `blur(${props.blur})`,
y: props.yOffset,
};
}
function getAnimate() {
return {
opacity: 1,
filter: `blur(0px)`,
y: 0,
};
}
Each element begins invisible, blurred, and slightly displaced. As it enters view, it regains clarity — fading and rising into position. It feels less like an animation, and more like focus returning to a lens.
Applying motion
<Motion
v-for="(child, index) in children"
:key="index"
:initial="getInitial()"
:while-in-view="getAnimate()"
:transition="{
duration: props.duration,
easing: 'easeInOut',
delay: props.delay * index,
}"
>
<component :is="child" />
</Motion>
The while-in-view prop ensures the animation only happens when the element is visible —
making it both intentional and performant.
The slight stagger between children turns what could’ve been a simple fade into a composed sequence.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
duration | number | 1 | How long each animation lasts (in seconds). |
delay | number | 2 | Delay multiplier for each child. (Try smaller values like 0.2 for natural pacing.) |
blur | string | "20px" | The initial blur before revealing. |
yOffset | number | 20 | Vertical offset for the element’s starting position. |
class | string | "" | Additional classes for layout or spacing. |
Adjusting the feel
- To make the animation feel lighter, use a smaller
yOffset(like10). - To create faster rhythm, lower the delay to
0.15. - To keep the motion grounded, avoid combining too many transforms.
Small tweaks like these often matter more than big ones.
Things to keep in mind
- If the animation doesn’t trigger, make sure the element actually scrolls into view.
- If it feels slow, reduce the delay — it multiplies with the index.
- If blur seems missing, check if the parent container has
overflow: hidden;.
Performance remains smooth because only transform, opacity, and filter are animated —
the properties that browsers handle most efficiently.
Extending it
BlurReveal is intentionally minimal, but it can grow with your needs. You can add:
- Directional movement (
"up" | "down" | "left" | "right") - Custom easing functions (
"easeOutBack","spring") - Options to trigger once or on every scroll
The goal is not to make it complex, but expressive — motion that adapts to the story you’re telling.
Final thought
Good animation doesn’t draw attention to itself. It makes everything else feel better.
BlurReveal isn’t about showing off motion — it’s about crafting flow. Every fade, every blur, every slight movement serves one purpose: to make the interface feel alive, connected, and deliberate.
Sometimes, that’s all a page needs — a small pause, a little motion, and the patience to make it feel right.
Understanding Dijkstra’s Algorithm: A Comprehensive Guide with Implementation in C
Dive into Dijkstra's Algorithm with this detailed guide. Learn its logic, pseudo code, and C implementation step-by-step.
Animated Beam: Visual Connections Between Elements in Vue
Animated Beam draws a glowing, animated path between two elements — expressing flow, interaction, and connection through motion.