Word Flip
snap-cn/word-flipFreeComponent
A headline types itself out, then one word cycles on a 3D flip — it sinks into an anticipation dip, throws up and away about its baseline under motion blur, and the next word unfurls from below. The slot is reserved from the first keystroke, so nothing in the sentence ever reflows.
Install it
npx shadcn@latest add https://snapcn.dev/r/word-flip.jsonSource
1"use client";23import { useEffect, useRef, useState } from "react";4import {5 AbsoluteFill,6 continueRender,7 delayRender,8 Easing,9 getRemotionEnvironment,10 interpolate,11 useCurrentFrame,12 useVideoConfig,13} from "remotion";14import { mixOklch, type SnapCnTheme, useSnapCnTheme } from "@/lib/snap-cn-ui";1516/**17 * Pure animation math for WordFlip. Everything above the component is18 * frame-deterministic and side-effect free so it can be unit tested.19 */2021const CLAMP = {22 extrapolateLeft: "clamp",23 extrapolateRight: "clamp",24} as const;2526/** Frames each character takes, from a characters-per-second rate. */27export function framesPerChar(cps: number, fps: number): number {28 return fps / Math.max(0.001, cps);29}3031/**32 * A deterministic, seedless wobble on the keystroke clock so the typing does not33 * land on a metronome. Real typing is uneven; a perfectly regular cadence is the34 * single thing that gives a "typewriter" away. Bounded to ±`jitter` of one35 * keystroke and summed into an *offset*, never a per-key delay, so the sentence36 * still finishes exactly when the timeline says it does.37 */38export function keystrokeOffset(index: number, jitter: number): number {39 if (jitter <= 0) return 0;40 // Two incommensurable sines: no repeat over any realistic sentence, and no RNG,41 // so a render and the Player agree frame for frame.42 const w = Math.sin(index * 12.9898) * 0.6 + Math.sin(index * 4.1414) * 0.4;43 return w * jitter;44}4546/** Frame at which character `index` starts to appear. */47export function charStartFrame(48 index: number,49 opts: { typeStart: number; cps: number; fps: number; jitter: number },50): number {51 const per = framesPerChar(opts.cps, opts.fps);52 return (53 opts.typeStart + index * per + keystrokeOffset(index, opts.jitter) * per54 );55}5657/** Frame the last character has finished fading in. */58export function typingEndFrame(opts: {59 charCount: number;60 typeStart: number;61 cps: number;62 fps: number;63 jitter: number;64 charFade: number;65}): number {66 const last = charStartFrame(Math.max(0, opts.charCount - 1), opts);67 return last + opts.charFade;68}6970/** Frame at which flip `i` (0-based) begins. */71export function flipStartFrame(72 i: number,73 opts: { typingEnd: number; pause: number; cycle: number },74): number {75 return opts.typingEnd + opts.pause + i * opts.cycle;76}7778/**79 * Which word is showing at `frame`, and how far into its flip we are.80 *81 * `index` is the word entering (or resting); `local` is frames since that82// … truncated
What it pulls in
npm packages
Other registry items
Files it writes
More from snap-cn
All 24 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Answer Streamanswer-stream | snap-cn | Components | Free | 2 deps | |
| UI Caretcaret | snap-cn | Components | Free | 1 dep | |
| Follower Rushfollower-rush | snap-cn | Components | Free | 2 deps | |
| Product Herohero-launch | snap-cn | Components | Free | 2 deps | |
| UI Inputinput | snap-cn | Components | Free | 2 deps · 2 files | |
| Karaoke Captionskaraoke-captions | snap-cn | Components | Free | 2 deps | |
| Laptop Framelaptop-frame | snap-cn | Components | Free | 1 dep | |
| Logo Assemblelogo-assemble | snap-cn | Components | Free | 1 dep |
