Cascade Text
atlas-ui/cascade-textFreeComponent
A smooth line-by-line text reveal animation.
Live preview
live · rendered by the registry
Rendered by atlas-ui on their own page — this is the component running, not a screenshot of it.Install it
npx shadcn@latest add https://atlasui.dev/r/cascade-text.jsonSource
1"use client";23import React, { useEffect, useRef } from "react";45import {6 animate,7 cubicBezier,8 inView,9 stagger,10 useReducedMotion,11} from "motion/react";1213interface CascadeTextProps {14 children: React.ReactNode;15 animateOnScroll?: boolean;16 delay?: number;17}1819type PreparedElement = {20 element: HTMLElement;21 originalHTML: string;22 originalTextIndent: string;23 lines: HTMLElement[];24};2526const getTargetElements = (container: HTMLElement): HTMLElement[] => {27 if (container.hasAttribute("data-cascade-text")) {28 return Array.from(container.children) as HTMLElement[];29 }3031 return [container];32};3334const splitIntoLines = (35 element: HTMLElement,36 reduceMotion: boolean37): HTMLElement[] => {38 const text = (element.textContent ?? "").trim();3940 if (!text) {41 element.textContent = "";42 return [];43 }4445 const words = text.split(/\s+/).filter(Boolean);46 const fragment = document.createDocumentFragment();47 const wordSpans: HTMLSpanElement[] = [];4849 for (let index = 0; index < words.length; index += 1) {50 const span = document.createElement("span");51 span.textContent = words[index];52 span.style.display = "inline-block";5354 wordSpans.push(span);55 fragment.appendChild(span);5657 if (index < words.length - 1) {58 fragment.appendChild(document.createTextNode(" "));59 }60 }6162 element.textContent = "";63 element.appendChild(fragment);6465 const linesText: string[] = [];66 let currentTop: number | null = null;67 let currentWords: string[] = [];6869 wordSpans.forEach((span) => {70 const top = span.offsetTop;71 const value = span.textContent ?? "";7273 if (currentTop === null) {74 currentTop = top;75 currentWords.push(value);76 return;77 }7879 if (top !== currentTop) {80 linesText.push(currentWords.join(" "));81 currentWords = [value];82 currentTop = top;83 return;84 }8586 currentWords.push(value);87 });8889 if (currentWords.length > 0) {90 linesText.push(currentWords.join(" "));91 }9293 element.textContent = "";9495 const lines: HTMLElement[] = [];9697 linesText.forEach((lineText) => {98 const mask = document.createElement("span");99 mask.style.display = "block";100 mask.style.overflow = "hidden";101102 const line = document.createElement("span");103 line.textContent = lineText;104 line.style.display = "block";105 line.style.willChange = reduceMotion ? "opacity" : "transform";106107 if (reduceMotion) {108 line.style.opacity = "0";109 } else {110// … truncated
What it pulls in
npm packages
Files it writes
More from atlas-ui
All 99 items| Component | Registry | Kind | Access | Installs | Command |
|---|---|---|---|---|---|
| Animated Mobile Navbaranimated-mobile-navbar | atlas-ui | Components | Free | 2 deps | |
| Blend Mouse Trailerblend-mouse-trailer | atlas-ui | Components | Free | 1 dep | |
| Blob Mouse Trailerblob-mouse-trailer | atlas-ui | Components | Free | 1 dep | |
| Box Revealbox-reveal | atlas-ui | Components | Free | 1 dep | |
| Dot And Circle Mouse Trailerdot-and-circle-mouse-trailer | atlas-ui | Components | Free | 1 dep | |
| Fancy Theme Togglefancy-theme-toggle | atlas-ui | Components | Free | 1 dep | |
| Hacker Texthacker-text | atlas-ui | Components | Free | no deps | |
| Letter Swapletter-swap | atlas-ui | Components | Free | 1 dep |
