caption-highlight
remotionui/caption-highlightFreeComponent
TikTok-style word-by-word caption highlight
Live preview
live · rendered by the registry
Rendered by remotionui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://remotionui.com/r/caption-highlight.jsonSource
1import type { TikTokPage } from "@remotion/captions";2import { Fragment } from "react";3import {4 interpolate,5 interpolateColors,6 useCurrentFrame,7 useVideoConfig,8} from "remotion";9import {10 getAbsoluteTimeMs,11 getTokenEmphasis,12} from "@/remotion/lib/caption-utils";13import { scaleFont } from "@/remotion/lib/layout";14import { EASING, EMPHASIS } from "@/remotion/lib/motion-tokens";1516export type CaptionHighlightProps = {17 page: TikTokPage;18 activeColor?: string;19 inactiveColor?: string;20 fontSize?: number;21 fontWeight?: number | string;22 activeWeight?: number | string;23 textAlign?: "left" | "center";24 lineHeight?: number;25 /** Peak scale of the active word. Defaults to `EMPHASIS.subtle`. */26 emphasisScale?: number;27 /**28 * Optional frame override.29 * Pass a parent `frame` when using inside `<Sequence from={...}>`.30 */31 frame?: number;32};3334const clamp01 = (value: number) => Math.min(1, Math.max(0, value));3536/**37 * Caption tokens carry their own leading space. It has to sit outside the38 * scaled box — a word that grows inside its own space closes the gap to the39 * word before it.40 */41function splitLeadingSpace(text: string) {42 const leading = /^\s+/.exec(text)?.[0] ?? "";43 return { leading, word: text.slice(leading.length) };44}4546export const CaptionHighlight: React.FC<CaptionHighlightProps> = ({47 page,48 activeColor = "#ff6b00",49 inactiveColor = "#111111",50 fontSize: fontSizeProp,51 fontWeight = 650,52 activeWeight = 800,53 textAlign = "center",54 lineHeight = 1.12,55 emphasisScale = EMPHASIS.subtle,56 frame: frameOverride,57}) => {58 const localFrame = useCurrentFrame();59 const frame = frameOverride ?? localFrame;60 const { fps, width } = useVideoConfig();61 const fontSize = fontSizeProp ?? scaleFont(64, width);62 const absoluteTimeMs = getAbsoluteTimeMs(page, frame, fps);6364 return (65 <div66 style={{67 color: inactiveColor,68 fontSize,69 fontWeight,70 letterSpacing: 0,71 // The active word grows into the gaps on both sides, so the resting72 // rhythm has to be wider than a bare space character.73 wordSpacing: "0.12em",74 lineHeight,75 textAlign,76 whiteSpace: "pre-wrap",77 }}78 >79 {page.tokens.map((token) => {80 const emphasis = clamp01(getTokenEmphasis(frame, token, page, fps));81 // One ramp drives colour, weight, size and opacity — the word is82 // emphasised as a single gesture rather than a colour swap.83// … truncated
More from remotionui
All 127 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| audio-pulseaudio-pulse | remotionui | Components | Free | no deps | |
| audiogram-barsaudiogram-bars | remotionui | Components | Free | no deps | |
| blur-focus-inblur-focus-in | remotionui | Components | Free | no deps | |
| blur-inblur-in | remotionui | Components | Free | no deps | |
| blur-revealblur-reveal | remotionui | Components | Free | no deps | |
| chromatic-aberration-wipechromatic-aberration-wipe | remotionui | Components | Free | no deps | |
| confetti-burstconfetti-burst | remotionui | Components | Free | no deps | |
| countercounter | remotionui | Components | Free | no deps |
