Blur Out Up
The string is split on spaces and each word starts 6px out of focus, 10px
below its resting position and fully transparent, then eases to sharp, opaque and
in place. Word i starts 28ms after word i - 1.
Same idea as Soft Blur In at a larger unit and a
smaller amplitude: 6px of blur and 10px of travel rather than 12px and
16px, because a word is a bigger object and a blurred word is already hard
enough to read.
Usage
<template>
<MBlurOutUp
class="text-3xl font-bold"
text="Words lift out of focus"
/>
</template>
Examples
On running prose
<template>
<div class="max-w-sm">
<MBlurOutUp
class="text-lg leading-relaxed"
:stagger="14"
text="Running prose blurs up a word at a time, which keeps the paragraph readable while it is still arriving."
/>
</div>
</template>
The narrower stagger matters here. At the 28ms default a thirteen-word sentence
takes over 900ms to finish arriving, which reads as deliberate on a headline and
as sluggish on a paragraph.
On scroll
28ms per word, once the headline is on screen.
<template>
<div class="text-center">
<MBlurOutUp
class="text-3xl font-bold"
text="Plays once on scroll"
trigger-on-view
/>
<p class="mt-3 text-sm text-muted">
28ms per word, once the headline is on screen.
</p>
</div>
</template>
Set trigger-on-view to hold the reveal until the element enters the viewport,
which is what you want for a headline that sits below the fold. Observation stops
after the first intersection, so the effect never replays on the way back up.
Customize
Customize
Props
| Prop | Type | Default | Description |
|---|---|---|---|
textrequired | string | - | The text to reveal. Split into one animated element per word. |
stagger | number | 28 | Per-word stagger, in milliseconds. 0 resolves every word at once. Above about
120 a sentence takes seconds to finish. |
delay | number | 0 | Delay before the first word starts, in milliseconds. |
triggerOnView | boolean | false | Wait for the element to scroll into view before playing. Ignored under reduced motion. |
class | string | - | Extra classes for the root element, which is also the element carrying the
aria-label. |
Accessibility
The root span carries aria-label="<text>" and every animated word span — plus
the space separators between them — is aria-hidden="true". Announcing the words
individually would mean hearing the sentence read out word by word, interleaved
with the effect already showing it.
Under prefers-reduced-motion the reveal is run with duration: 0 rather than
skipped. motion-v treats "nothing to animate" as "write nothing", so skipping the
animation would leave the blurred, transparent, lowered starting state in place
forever. Collapsing the duration lands every word at its resting value within a
frame while keeping one identical server and client tree.
Performance
Each word animates opacity, filter and y only, so the browser can run the
reveal on the compositor and never touches layout. The cost that remains is the
blur(), which is proportional to the painted area of every blurred word; keep
the effect on headlines and short blocks rather than on a long paragraph that is
on screen for only a moment.
The whole string is a single once observer. Nothing is scheduled after the last
word lands, and there is no scroll listener — trigger-on-view uses an
IntersectionObserver, not a scroll handler.
How the timing works
Word i waits delay + i × stagger milliseconds, then animates for 560ms. The
whole string is finished when the last word's 560ms is over, which at the
28ms default is a five-word headline finishing in about 670ms.
The ease is (0.22, 1, 0.36, 1) — the same curve as
Soft Blur In and
Mask Reveal Up, shared so that a headline built from
several of these components resolves with one consistent motion rather than three.
Splitting per word rather than per character is what makes it usable on prose. The
same sentence as characters would take over 1.4s
(see Soft Blur In); as words it
finishes in well under a second and never reads as a typewriter.
It wraps like prose
The root span sets no display style, so it computes to display: inline,
and each word is an inline-block span. A long sentence therefore wraps at the
width of its parent, breaking across as many lines as it needs, with words still
arriving in reading order.
The consequence is that max-width has no effect on the component itself; wrap it
in a sized parent instead:
<div class="max-w-sm">
<MBlurOutUp text="Words lift out of focus" />
</div>
This is the same trade Mask Reveal Up makes for its own reasons — see that page for the line-based alternative.
Notes
- The component plays once.
triggerOnViewobserves withonce, and there is no replay prop — remount with a changing:keyto run it again. - Splitting is on a single space. A run of two spaces produces an empty word, which renders nothing but still consumes a stagger step. Harmless for prose, and the reason space separators are emitted between every pair of words rather than reconstructed from the original spacing.
- Word spans and space separators are both
white-space: pre, which is what keeps a wrapped sentence from losing its inter-word spacing at a line break. - Unlike Ransom Note,
staggerhere is in milliseconds, as it is everywhere else in this library.