DEV Community

Cover image for USAL.js — How I Built a 9KB Animation Library That Solves 99% of Scroll Animation Cases
Italo Almeida
Italo Almeida

Posted on

USAL.js — How I Built a 9KB Animation Library That Solves 99% of Scroll Animation Cases

Building USAL.js: From Weekend Project to Animation Game-Changer

Three months ago, I was working on a project that needed smooth text animations. You know, the fancy stuff where words appear one by one as you scroll, or letters shimmer with that premium feel.

I looked at the existing options: AOS.js and SAL.js. They're great libraries, don't get me wrong, but they had limitations that frustrated me:

  • No text-specific animations (word-by-word, letter-by-letter)
  • Limited framework support (especially for web components)
  • Basic animation sets that felt… well, basic
  • Configuration complexity for simple tasks

So I did what any developer does when existing tools don't fit: I built my own.

USAL.js started as a weekend project and evolved into something I never expected - a complete animation ecosystem that works everywhere, weighs almost nothing, and solves problems I didn't even know I had.

The Problem That Started Everything

I was building a landing page for a SaaS product. The client wanted that "wow factor" - text that animates word by word, numbers that count up, elements that fade in with perfect timing.

I tried extending AOS.js for text animations, but it felt like forcing a square peg into a round hole. The bundle was getting heavy, the API was getting messy, and I still couldn't animate text the way I wanted.

That's when I realized: there wasn't a single library that handled both element animations AND text animations elegantly.

Building From the Ground Up

I started with SAL.js as inspiration for the basic structure - I loved its simplicity. But as I added features, I realized I needed to rebuild everything from scratch to achieve what I wanted:

The Requirements I Set

  • Tiny footprint - Had to be under 10KB
  • Framework agnostic - Work with React, Vue, Svelte, Angular, AND vanilla JS
  • Web Components support - Something almost no library handles well
  • Rich text animations - The main reason I started this
  • Tailwind-inspired syntax - Because developers love that approach
  • Zero dependencies - No external requirements
  • 60fps performance - Smooth on all devices

The Technical Challenges

Challenge 1: Text Animation Architecture

The biggest hurdle was creating a system that could split text by words, letters, or items, then animate each piece individually with staggered timing. This required:

// Splitting text while preserving HTML structure
// Handling different languages and character sets  
// Managing thousands of micro-animations efficiently
// Cleaning up DOM when animations complete
Enter fullscreen mode Exit fullscreen mode

Challenge 2: Framework Compatibility

Each framework has its own way of handling DOM updates, component lifecycles, and reactive data. I needed a core that could adapt:

// Core vanilla JS library
// React wrapper with useEffect hooks
// Vue wrapper with composition API
// Svelte wrapper with actions
// Angular wrapper with directives
// Web Components with custom elements
Enter fullscreen mode Exit fullscreen mode

Challenge 3: Performance Optimization

With potentially hundreds of animated elements, I needed smart throttling:

// Intersection Observer for viewport detection
// Request Animation Frame for smooth updates
// Memory management for dynamic content
// Concurrent animation limiting
// GPU acceleration where possible
Enter fullscreen mode Exit fullscreen mode

The Breakthrough: Tailwind-Inspired Syntax

The turning point was adopting a Tailwind-like approach. Instead of complex configuration objects, why not use simple, chainable attributes?

Before (complex):

new Animation({
  element: '.title',
  animation: 'fadeUp',
  duration: 800,
  delay: 200,
  easing: 'ease-out'
});
Enter fullscreen mode Exit fullscreen mode

After (simple):

<h1 data-usal="fade-u duration-800 delay-200 ease-out">Title</h1>
Enter fullscreen mode Exit fullscreen mode

This syntax felt natural to developers already using Tailwind, and it made the library incredibly approachable.

What Makes USAL.js Different

Split Animations
Count Animations
Text Effects
Animations

1. Text Animations That Actually Work

<!-- Each word appears separately -->
<p data-usal="split-word split-fade-u split-delay-200">
  Your message appears word by word
</p>

<!-- Each letter gets a shimmer effect -->
<h1 data-usal="text-shimmer split-letter">
  PREMIUM FEELING TEXT
</h1>

<!-- Fluid weight animation per letter -->
<h2 data-usal="text-fluid split-letter duration-3000">
  Dynamic Typography
</h2>
Enter fullscreen mode Exit fullscreen mode

2. Number Counters Built-In

<!-- Counts from 0 to target number -->
<span data-usal="count-[1234]">1234</span>
<span data-usal="count-[98.5]">98.5%</span>
<span data-usal="count-[42,350]">$42,350</span>
Enter fullscreen mode Exit fullscreen mode

3. Framework Integration Done Right

React/Next.js Setup:

npm install @usal/react
Enter fullscreen mode Exit fullscreen mode
// app.js or layout.js
import { USALProvider } from '@usal/react';

export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        <USALProvider>
          {children}
        </USALProvider>
      </body>
    </html>
  );
}

// Any component
function Hero() {
  return (
    <div>
      <h1 data-usal="fade-u duration-1200">Welcome</h1>
      <p data-usal="split-word split-fade-r split-delay-100">
        Each word slides in from right
      </p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Solidjs/SolidStart Setup:

npm install @usal/solid
Enter fullscreen mode Exit fullscreen mode
// app.tsx
import { USALProvider } from '@usal/solid';

export default function App() {
  return (
    <Router
      root={(props) => (
          <Suspense>
            <USALProvider>
              {props.children}
            </USALProvider>
          </Suspense>
      )}
    >
      <FileRoutes />
    </Router>
  );
}

// Any component
function Hero() {
  return (
    <div>
      <h1 data-usal="fade-u duration-1200">Welcome</h1>
      <p data-usal="split-word split-fade-r split-delay-100">
        Each word slides in from right
      </p>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

Vue/Nuxt Setup:

npm install @usal/vue
Enter fullscreen mode Exit fullscreen mode
// nuxt.config.js
export default defineNuxtConfig({
  modules: ['@usal/vue/nuxt']
});
Enter fullscreen mode Exit fullscreen mode
<!-- pages/index.vue -->
<template>
  <div>
    <h1 v-usal="'fade-u duration-800'">Vue Animations</h1>
    <div v-usal="'split-item split-zoomin split-delay-150'">
      <div>Item 1</div>
      <div>Item 2</div>
      <div>Item 3</div>
    </div>
  </div>
</template>
Enter fullscreen mode Exit fullscreen mode

Svelte/SvelteKit Setup:

npm install @usal/svelte
Enter fullscreen mode Exit fullscreen mode
<!-- Any component -->
<script>
  import { usal } from '@usal/svelte';
</script>

<div use:usal={'fade-u duration-800'}>
  Svelte animation
</div>

<p use:usal={'split-letter split-text-shimmer'}>
  Shimmer effect in Svelte
</p>
Enter fullscreen mode Exit fullscreen mode

Angular Setup:

npm install @usal/angular
Enter fullscreen mode Exit fullscreen mode
// app.component.ts
import { USALModule } from '@usal/angular';

@Component({
  imports: [USALModule],
  template: `
    <div [usal]="'fade-u duration-800'">
      Angular animation
    </div>
    <h1 [usal]="'split-word split-flip-r split-delay-200'">
      Each word flips in
    </h1>
  `
})
export class AppComponent { }
Enter fullscreen mode Exit fullscreen mode

Web Components (The Game Changer):

npm install @usal/lit
Enter fullscreen mode Exit fullscreen mode
import { LitElement, html } from 'lit';
import { useUSAL } from '@usal/lit';
const usalInstance = useUSAL();
import { usal } from '@usal/lit';

class MyElement extends LitElement {
  render() {
    return html`
      <div ${usal('fade-u duration-800')}>
        Web Component with animations
      </div>
    `;
  }
}
Enter fullscreen mode Exit fullscreen mode

Vanilla JavaScript (Zero Setup):

<script src="https://cdn.jsdelivr.net/npm/usal@latest/usal.min.js"></script>
<div data-usal="fade-u">Instant animations</div>
Enter fullscreen mode Exit fullscreen mode

The Numbers That Matter

After three months of development and optimization:

  • 9KB minified (smaller than a typical favicon)
  • 40+ animations (fade, zoom, flip with all directions)
  • 60fps performance (tested with 500+ elements)
  • Zero dependencies (no external requirements)
  • 6 framework integrations (more than any competitor)
  • Web Components support (unique feature)

Real-World Usage Examples

E-commerce Product Page

<!-- Hero section -->
<div class="product-hero">
  <img data-usal="zoomin duration-800" src="product.jpg">
  <h1 data-usal="split-word split-fade-u split-delay-150">
    Revolutionary Product Name
  </h1>
  <p data-usal="fade-u delay-600">Perfect for your needs</p>
</div>

<!-- Stats section -->
<div class="stats">
  <div data-usal="fade-u delay-200">
    <span data-usal="count-[1000] duration-2000">1000</span>
    <p>Happy Customers</p>
  </div>
  <div data-usal="fade-u delay-400">
    <span data-usal="count-[4.9] duration-2000">4.9</span>
    <p>Average Rating</p>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

SaaS Landing Page

<!-- Feature cards -->
<div class="features" data-usal="split-item split-fade-u split-delay-200">
  <div class="feature-card">
    <h3>Fast Performance</h3>
    <p>Lightning quick animations</p>
  </div>
  <div class="feature-card">
    <h3>Easy Setup</h3>
    <p>One attribute, infinite possibilities</p>
  </div>
  <div class="feature-card">
    <h3>Framework Agnostic</h3>
    <p>Works everywhere</p>
  </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Blog Article Enhancement

<article>
  <h1 data-usal="split-word split-fade-r split-delay-100">
    How to Build Better Web Experiences
  </h1>

  <div data-usal="fade-u delay-800" class="article-meta">
    <span data-usal="count-[5] duration-1000">5</span> min read
  </div>

  <div class="content" data-usal="split-item split-fade-u split-delay-300">
    <p>First paragraph slides in</p>
    <p>Second paragraph follows</p>
    <p>Creating reading rhythm</p>
  </div>
</article>
Enter fullscreen mode Exit fullscreen mode

Lessons Learned

What Worked

  • Tailwind-inspired syntax - Developers adopted it immediately
  • Framework-first approach - Each integration feels native
  • Text animations - The killer feature that differentiated us
  • Performance focus - Never compromised on speed
  • Web Components support - Future-proofing paid off

What Was Challenging

  • Cross-browser testing - Especially for text animations
  • Memory management - With thousands of animated elements
  • Framework integration - Each has unique quirks
  • Bundle size optimization - Every byte mattered
  • Documentation - Making complex features feel simple

The Future of USAL.js

Based on community feedback, I'm working on:

  • More text effects (typewriter, glitch, gradient)
  • Timeline animations (choreographed sequences)
  • Scroll-triggered interactions (parallax, morphing)
  • Visual editor (drag-and-drop animation builder)
  • More framework support (Pure Vite, Qwik)

Try USAL.js Today

The easiest way to get started:

<!DOCTYPE html>
<html>
<head>
  <script src="https://cdn.jsdelivr.net/npm/usal@latest/usal.min.js"></script>
</head>
<body>
  <h1 data-usal="fade-u duration-1200">Hello USAL!</h1>
  <p data-usal="split-word split-fade-r split-delay-200">
    Each word appears from the right
  </p>
  <div data-usal="count-[100] duration-2000">100</div>
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Save this as an HTML file, open in your browser, and scroll. You'll see why I built this.

Why I Open-Sourced It

I could have kept USAL.js as a commercial product, but I believe great tools should be accessible to everyone. The web animation ecosystem needed something that just works, and I hope USAL fills that gap.

Building USAL taught me that sometimes the best solution isn't the most complex one - it's the one that gets out of your way and lets you focus on creating great experiences.


Ready to try USAL.js?

What animations will you build with USAL? I'd love to see your creations - tag me and share your projects!

Follow me for more insights on building developer tools that people actually want to use.

Top comments (0)