惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

L
LangChain Blog
S
SegmentFault 最新的问题
V
Visual Studio Blog
J
Java Code Geeks
宝玉的分享
宝玉的分享
美团技术团队
博客园 - Franky
酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hackread – Cybersecurity News, Data Breaches, AI and More
有赞技术团队
有赞技术团队
量子位
Martin Fowler
Martin Fowler
MyScale Blog
MyScale Blog
Google DeepMind News
Google DeepMind News
Jina AI
Jina AI
博客园 - 叶小钗
月光博客
月光博客
P
Proofpoint News Feed
D
DataBreaches.Net
Blog — PlanetScale
Blog — PlanetScale
博客园_首页
腾讯CDC
Microsoft Azure Blog
Microsoft Azure Blog
Stack Overflow Blog
Stack Overflow Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
Meet @vysmo/text - 243 text animation presets in 3 KB
TommyDee · 2026-05-20 · via DEV Community

The big text on a landing page deserves to arrive. The headline you spent two hours writing shouldn't just appear — it should choreograph itself into place, letter by letter, so the reader's eye lands on it and stays.

That's what @vysmo/text does. It's a new addition to the vysmo libraries — a tiny, tree-shakable, zero-dependency text animation library with 243 presets and one of the cleanest APIs I've used.

This is a tour.

Text Animations

Three lines

The simplest possible use:

import { animateText } from "@vysmo/text";

animateText(element, { preset: "enter/fade-up" });

Enter fullscreen mode Exit fullscreen mode

That's a real, shippable hero animation. The library splits the text into grapheme-safe slices (more on that below), applies the preset's choreography, and starts playing on mount. No timeline setup, no .from() / .to() chains, no ref management.

If you'd rather have a different feel, change the preset name:

animateText(element, { preset: "enter/elastic-rise" });   // soft spring
animateText(element, { preset: "enter/bloom-scatter" });  // letters arrive from depth
animateText(element, { preset: "enter/whirl-scatter-curl" }); // dramatic, full-on
animateText(element, { preset: "enter/blur-in" });        // tasteful, agency-deck-ready

Enter fullscreen mode Exit fullscreen mode

Same one line. 120 enter presets to choose from. The catalog at vysmo.com/text has all of them with a live playground — click one, type your text, see it.

243 presets, 3 KB

The headline number is real: 229 generated + 14 curated = 243 presets, in roughly 3 KB gzipped. That's a smaller bundle than most loading spinners.

The trick is tree-shaking. Each preset is its own ES module export. You import what you ship:

import { animateText, fadeUp } from "@vysmo/text";

animateText(element, { preset: fadeUp });  // pass the object, not the string

Enter fullscreen mode Exit fullscreen mode

When you pass the preset by reference, only that preset's data lands in your bundle. The other 242 stay tree-shaken out. The string-name form ("enter/fade-up") pulls in a tiny registry — pick whichever ergonomics you prefer.

For a typical landing page using two or three presets, you ship under 2 KB total. For a kinetic-typography portfolio site that uses twenty different presets, you ship maybe 4 KB. Either way, less than a logo SVG.

The presets are split into three categories

Enter (120) — animations for text arriving. fade-up, elastic-rise, bloom-scatter, flip-up-spring, tunnel, plus 115 more. Tuned to feel deliberate, not gimmicky. Most run for ~600–900 ms.

Exit (92) — animations for text leaving. The conceptual mirror of enter, but harder to design well — exits need to feel intentional, not glitchy. fade-down, mist-out, collapse-burst, pinwheel-out. Most react well to being driven faster than their default duration.

Emphasis (31) — animations that loop or play on demand to draw attention. pulse, shake, wobble, coin-flip, spin. These are what you wire to a button click or a "10 items left" badge.

A preset is just data:

import { fadeUp } from "@vysmo/text";

console.log(fadeUp);
// {
//   name: "enter/fade-up",
//   split: "character",
//   stagger: 30,
//   animations: [
//     { prop: "opacity",    from: 0,  to: 1, duration: 500, ease: power2.out },
//     { prop: "translateY", from: 20, to: 0, duration: 500, ease: power2.out },
//   ],
// }

Enter fullscreen mode Exit fullscreen mode

No classes. No inheritance. No registry magic. You can console.log a preset and read it. You can copy one and modify it. You can author your own with the exact same shape.

Two knobs that change everything

Every preset has a default — but the API lets you override any knob per call. Two of them, in particular, are the cheapest way to make the same preset feel completely different.

stagger is the milliseconds between consecutive slices starting. The default is 30 ms (energetic, fast). Bump it to 80 ms and the same preset feels slow and contemplative. Drop it to 10 ms and slices arrive almost together — more of a wash than a choreography.

animateText(element, { preset: "enter/fade-up", stagger: 80 }); // thoughtful
animateText(element, { preset: "enter/fade-up", stagger: 10 }); // urgent

Enter fullscreen mode Exit fullscreen mode

staggerOrder is who gets staggered first. Default is "start" (left-to-right, like reading). But:

animateText(element, { preset: "enter/fade-up", staggerOrder: "end" });    // right-to-left
animateText(element, { preset: "enter/fade-up", staggerOrder: "center" }); // middle outward
animateText(element, { preset: "enter/fade-up", staggerOrder: "edges" }); // both ends inward
animateText(element, { preset: "enter/fade-up", staggerOrder: "random" }); // chaos mode

Enter fullscreen mode Exit fullscreen mode

"center" is the secret weapon. Try it on a hero headline — letters bloom outward from the middle. It feels like the title is being projected rather than typed. Most libraries don't expose this and people don't realize how much they're missing.

Scrolling drives it for free

The handle that animateText returns has a .seek(progress) method that takes a value between 0 and 1 and snaps the animation to that point in its timeline. That's the only thing you need to drive a text animation from scroll position:

const handle = animateText(headline, {
  preset: "enter/bloom-scatter",
  autoPlay: false,
});

// In a scroll handler — pair with @vysmo/scroll, ScrollTrigger, or your own IntersectionObserver:
function onScroll(progress: number) {
  handle.seek(progress);
}

Enter fullscreen mode Exit fullscreen mode

Cinematic word-by-word reveals tied to scroll position. Two lines. The reason this works cleanly is the library's single-master-clock architecture — every slice is animated against the same time origin, so seeking is coherent. That's worth a post on its own (it's coming).

The split is grapheme-safe

One detail that separates this library from most of its predecessors: the text splitting uses Intl.Segmenter. Which means it correctly handles emoji clusters, regional flags, combining marks, and connected scripts.

// Most libraries:
[..."👨‍👩‍👧"];  // ['👨','‍','👩','‍','👧']  — family emoji shattered into 5 broken pieces

// @vysmo/text:
splitText("👨‍👩‍👧", { mode: "character" }).slices.length;  // 1 — correct

Enter fullscreen mode Exit fullscreen mode

If your animation has ever broken for users in India, Japan, the UAE, or anywhere with an emoji-rich messaging culture, this is why. The fix has been in browsers since 2020 and most libraries still don't use it. Vysmo does, by default.

Word-mode and line-mode use the same Segmenter, which means they also work correctly in Thai (no spaces between words), Japanese (mixed scripts), and Arabic (right-to-left). If you ship to a global audience by default — which, in 2026, is everyone — this matters.

React, too

There's a thin companion package, @vysmo/text-react, that wraps the runtime in a declarative component plus a useAnimateText hook:

import { AnimateText } from "@vysmo/text-react";

export function Hero() {
  return (
    <AnimateText as="h1" preset="enter/fade-up">
      Hello world
    </AnimateText>
  );
}

Enter fullscreen mode Exit fullscreen mode

That's the whole component. Props pass through to the underlying runtime — preset, split, stagger, repeat, all of it.

Try it

pnpm add @vysmo/text

Enter fullscreen mode Exit fullscreen mode

Then three lines:

import { animateText } from "@vysmo/text";
const h1 = document.querySelector("h1")!;
animateText(h1, { preset: "enter/bloom-scatter" });

Enter fullscreen mode Exit fullscreen mode

Open vysmo.com/text for the live catalog and the studio that lets you tweak every knob. Source and issue tracker at github.com/vysmodev/vysmo.

Vysmo libraries are MIT, free, and zero-dependency — there's no commercial tier, no rate limit, no telemetry. Just one library doing one thing as cleanly as I could get it.