Vertical cut reveal

When to use
Use when a line of type must arrive hard-edged and fast: every character (or word, or line) rises from under its own invisible edge, one after another, and the stagger can start at the first item, the last, the middle or any index. It is a port of the vertical-cut-reveal component of fancy, so the numbers below are the original's. Clip: vertical-cut-reveal.mp4. Demo: demo/vertical-cut-reveal.html.
The function is in the demo page (about 30 lines): copy it with the CSS. Time is the seek: there is no trigger, at is the second the animation starts.
The code
.vcr { display: flex; flex-wrap: wrap; white-space: pre-wrap; }
.vcr.lines { flex-direction: column; }
.vcr-word { display: inline-flex; overflow: hidden; } /* the clip box: one per word */
.vcr-char { position: relative; white-space: pre-wrap; }
.vcr-glyph { display: inline-block; } /* the part that moves */Easing and timing
The clip runs 2.4 s. The look is swiss.
Notes
import { motionSpring } from '../../core/motion/motion-spring.js';
// verticalCutReveal(el, text, { splitBy, staggerFrom, stagger, reverse, spring, delay, at, seed }) returns frame(t)
const spring = { stiffness: 200, damping: 21 };
const lines = [
verticalCutReveal(a, 'Cut from below,', { splitBy: 'characters', stagger: 0.025, spring, at: 0.1 }),
verticalCutReveal(b, 'then from above,', { splitBy: 'characters', stagger: 0.025, staggerFrom: 'last', reverse: true, delay: 0.5, spring, at: 0.1 }),
];
vawe.onFrame((t) => lines.forEach((f) => f(t)));
// inside: y = (reverse ? -100 : 100) * (1 - motionSpring(spring, 100)(t - at - delay - itemDelay)), as translateY(y%)The numbers (the original's defaults)
- Spring: stiffness 190, damping 22, mass 1 by default; the demo uses 200 and 21. The spring is the motion library's, in core/motion/motion-spring.js: it settles in about 0.5 s and overshoots by under 1 percent.
staggerDuration0.2 s by default (words). ForsplitBy: 'characters'use 0.025 s.staggerFrom:first,last,center,randomor an index. Center isfloor(total / 2), andtotalcounts the spaces between words, but the item index does not: forcharactersthe centre sits about one item to the right of the true middle. Random draws one centre per item from the seededseed.reversestarts each item at-100%(it falls into place) instead of100%.- Structure: one
overflow: hiddenbox per word, one inline-block glyph inside it. The box is the line height, so a tightline-heightclips descenders. Give the line 1.1 or more. splitBy:characters,words,lines(the container becomes a column) or any separator string.delayadds to every item (the original'stransition.delay).
Fidelity
Checked against the live component at 60 fps with a virtual clock. The y of every glyph matches the original to 0.2 percent of the line height, and each line's layout box matches to 0.06 px. The same frames match to an SSIM of 0.98 once the page's own buttons are masked; the rest is font smoothing.
The common mistake
Moving the clip box instead of the glyph, or clipping the whole line with one mask. The cut is per word: the glyph slides inside its own box, so words that have not started are invisible while the others already stand. For one mask over a whole line use mask-rise.
Credit: port of fancy by Daniel Petho, MIT, https://github.com/danielpetho/fancy (component vertical-cut-reveal, https://www.fancycomponents.dev/docs/components/text/vertical-cut-reveal).
Sound
Sound: none; a type reveal has no event to mark.





