Blog
Oct 12, 2025 - 6 MIN READ

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

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:

  1. Capture every child passed into the slot.
  2. Wrap each in a <Motion> component.
  3. Define an initial state — blurred, invisible, and slightly lower.
  4. Animate to the final state — sharp, visible, and aligned.
  5. 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

PropTypeDefaultDescription
durationnumber1How long each animation lasts (in seconds).
delaynumber2Delay multiplier for each child. (Try smaller values like 0.2 for natural pacing.)
blurstring"20px"The initial blur before revealing.
yOffsetnumber20Vertical offset for the element’s starting position.
classstring""Additional classes for layout or spacing.

Adjusting the feel

  • To make the animation feel lighter, use a smaller yOffset (like 10).
  • 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.

rahulv.dev | Built with Nuxt UI • © 1970