USAL
JS

Ultimate Scroll Animation Library
~8KB
Gzipped Size
120FPS
Performance
Zero
Dependencies
40+
Animations
WAAPI
rAF
Engine
Quick Start
Framework Setup
// Installed via CDN, initializes automatically, instance in window.USAL
// React (Next.js)
import { USALProvider } from '@usal/react';
<USALProvider>{children}</USALProvider>
// Solid (SolidStart)
import { USALProvider } from '@usal/solid';
<USALProvider>{props.children}</USALProvider>
// Svelte (SvelteKit)
import { usal } from '@usal/svelte';
// USAL auto-initializes globally
// Vue (Nuxt)
import { USALPlugin } from '@usal/vue';
createApp(App).use(USALPlugin).mount('#app');
//for Nuxt
export default defineNuxtConfig({
modules: ['@usal/vue/nuxt']
//...
// Lit
import { usal } from '@usal/lit';
// USAL auto-initializes globally
// Angular
import { USALModule } from '@usal/angular';
@Component({imports: [USALModule]})
export class AppComponent
See how simple it is to use
Main animation attribute
<!-- Vanilla JS -->
<div data-usal="fade duration-500">Content</div>
<!-- React/Next.js -->
<div data-usal="fade duration-500">Content</div>
<!-- Solid/SolidStart -->
<div data-usal="fade duration-500">Content</div>
<!-- Svelte/SvelteKit -->
<div use:usal={'fade duration-500'}>Content</div>
<!-- Vue/Nuxt -->
<div v-usal="'fade duration-500'">Content</div>
<!-- Lit -->
<div ${usal('fade duration-500')}>Content</div>
<!-- Angular -->
<div usal="fade duration-500">Content</div>
Basic Animations
Fade Up
fade
fade
Fade Up
fade-u
fade-u
Fade Down
fade-d
fade-d
Fade Left
fade-l
fade-l
Fade Right
fade-r
fade-r
Fade Up-Left
fade-ul
fade-ul
Fade Up-Right
fade-ur
fade-ur
Fade Down-Left
fade-dl
fade-dl
Fade Down-Right
fade-dr
fade-dr
Slide Up
slide
slide
Slide Up
slide-u
slide-u
Slide Down
slide-d
slide-d
Slide Left
slide-l
slide-l
Slide Right
slide-r
slide-r
Slide Up-Left
slide-ul
slide-ul
Slide Up-Right
slide-ur
slide-ur
Slide Down-Left
slide-dl
slide-dl
Slide Down-Right
slide-dr
slide-dr
<div data-usal="fade-u">Fade Up</div>
<div data-usal="slide-dr duration-800">Slide Down-Right</div>
<div data-usal="fade-l delay-200">Fade Left with Delay</div>
<div data-usal="slide-r forwards">Slide Right (stays)</div>
Zoom Animations
ZoomIn Up
zoomin
zoomin
ZoomIn Up
zoomin-u
zoomin-u
ZoomIn Down
zoomin-d
zoomin-d
ZoomIn Left
zoomin-l
zoomin-l
ZoomIn Right
zoomin-r
zoomin-r
ZoomIn Up-Left
zoomin-ul
zoomin-ul
ZoomIn Up-Right
zoomin-ur
zoomin-ur
ZoomIn Down-Left
zoomin-dl
zoomin-dl
ZoomIn Down-Right
zoomin-dr
zoomin-dr
ZoomOut Up
zoomout
zoomout
ZoomOut Up
zoomout-u
zoomout-u
ZoomOut Down
zoomout-d
zoomout-d
ZoomOut Left
zoomout-l
zoomout-l
ZoomOut Right
zoomout-r
zoomout-r
ZoomOut Up-Left
zoomout-ul
zoomout-ul
ZoomOut Up-Right
zoomout-ur
zoomout-ur
ZoomOut Down-Left
zoomout-dl
zoomout-dl
ZoomOut Down-Right
zoomout-dr
zoomout-dr
<div data-usal="zoomin">Scale from 60% to 100%</div>
<div data-usal="zoomout-ur blur duration-1200">Complex zoom</div>
Flip Animations
Flip Default
flip
flip
Flip Up
flip-u
flip-u
Flip Down
flip-d
flip-d
Flip Left
flip-l
flip-l
Flip Right
flip-r
flip-r
Flip Up-Left
flip-ul
flip-ul
Flip Up-Right
flip-ur
flip-ur
Flip Down-Left
flip-dl
flip-dl
Flip Down-Right
flip-dr
flip-dr
<div data-usal="flip-u">3D flip from top</div>
<div data-usal="flip-dr duration-1500">Diagonal flip</div>
Text Split Animations
Each Word Appears Separately
Each Letter Appears Separately
Zoom Out With Blur Per Word
Modern web development is all about smooth user experiences. Dream big, code bigger, animate everything in between.
Fluid Weight Animation
SHIMMER LETTER BY LETTER
<p data-usal="fade-u split-word split-delay-200">Each Word</p>
<p data-usal="fade-u split-letter split-delay-50">Each Letter</p>
<h2 data-usal="text-fluid split-letter duration-2000 split-delay-50">Fluid Weight</h2>
<p data-usal="text-shimmer split-letter duration-2000 split-delay-100">Shimmer</p>
Count Animations
1234
Users
98.5%
Success Rate
$42,350
Revenue
4.9/5
Rating
<div data-usal="count-[1234] duration-4000">1234</div>
<div data-usal="count-[98.5] duration-4000">98.5%</div>
Modifiers & Options
Once (no repeat)
fade-u once
fade-u once
Linear easing
zoomin linear
zoomin linear
Ease easing
flip-r ease
flip-r ease
Ease-in easing
fade-d ease-in
fade-d ease-in
Ease-out easing
zoomout duration-1000 easing-[steps(4)]
zoomout duration-1000 easing-[steps(4)]
2s + Blur
fade-ul blur duration-2000
fade-ul blur duration-2000
1s delay
flip-d delay-1000
flip-d delay-1000
50% threshold
zoomin-r threshold-50
zoomin-r threshold-50
<div data-usal="fade-u once">Animate only once</div>
<div data-usal="zoomin linear">Linear easing</div>
<div data-usal="fade-ul blur duration-2000">With blur</div>
Cards & Timing
Instant Animation
fade-u delay-0 duration-800
200ms Delay
zoomin delay-200 duration-1000
400ms Delay
flip-r delay-400 duration-1200
600ms Delay + Blur
fade-ur delay-600 duration-1500 blur
Split Items Animation
- Item A
- Item B
- Item C
- Item D
- Item E
- Item F
- Item G
- Item H
<ul data-usal="split-item split-fade-r split-delay-100">
<li>First item</li>
<li>Second item</li>
<li>Third item</li>
</ul>
Advanced Timeline
Loop Animation
fade-u loop
fade-u loop
Forwards State
line-[|50s+1.5o+50] forwards
line-[|50s+1.5o+50] forwards
Custom Blur
flip-r blur-3
flip-r blur-3
Tuned Movement
fade-40
fade-40
Tuned Zoom
zoomin-30-50
zoomin-30-50
Timeline Basic
line-[o+0 s+0.5 | 50 s+1.2 | o+100 s+1]
line-[o+0 s+0.5 | 50 s+1.2 | o+100 s+1]
Timeline 3D
line-[p+150 ry+0 | 50 ry+180 | ry+360]
line-[p+150 ry+0 | 50 ry+180 | ry+360]
Timeline Blur
line-[b+5 o+0 | 30 b+2 o+50 | b+0 o+100]
line-[b+5 o+0 | 30 b+2 o+50 | b+0 o+100]
Bang!
<!-- Loop Animation -->
<div data-usal="fade-u loop">Continuous loop</div>
<!-- Forwards State -->
<div data-usal="line-[|50s+1.5o+50] forwards">Keep final state</div>
<!-- Custom Blur -->
<div data-usal="flip-r blur-3">3rem blur effect</div>
<!-- Animation Tuning -->
<div data-usal="fade-40">40% movement distance</div>
<div data-usal="zoomin-30-50">30% movement, 50% intensity</div>
<div data-usal="flip-120-60">120° angle, 60rem perspective</div>
<!-- Timeline Animations -->
<div data-usal="line-[o+0 s+0.5 | 50 s+1.2 | o+100 s+1] duration-2000">
Multi-keyframe timeline
</div>
<div data-usal="line-[p+150 ry+0 | 50 ry+180 | ry+360] duration-3000">
3D rotation timeline
</div>
<div data-usal="line-[
p+150 ry+0
| 20 ry+1440
| 50 ry+2160
| 80 ry+2340
| 95 ry+2430 rx+30
| ry+2440 rx+90
] duration-5000 forwards">
Bang!
</div>
Dynamic Content Test
// Add items dynamically
const item = document.createElement('div');
item.textContent = 'New Item';
item.setAttribute('data-usal', 'fade-u duration-800');
parent.appendChild(item);
API Documentation
Property Values
Available Animations
Core animation types
// Basic animations
fade, fade-u, fade-d, fade-l, fade-r
fade-ul, fade-ur, fade-dl, fade-dr
// Slide animations
slide, slide-u, slide-d, slide-l, slide-r
slide-ul, slide-ur, slide-dl, slide-dr
// Zoom in animations
zoomin, zoomin-u, zoomin-d, zoomin-l, zoomin-r
zoomin-ul, zoomin-ur, zoomin-dl, zoomin-dr
// Zoom out animations
zoomout, zoomout-u, zoomout-d, zoomout-l, zoomout-r
zoomout-ul, zoomout-ur, zoomout-dl, zoomout-dr
// Flip animations
flip, flip-u, flip-d, flip-l, flip-r
flip-ul, flip-ur, flip-dl, flip-dr
Easing Functions
Control animation acceleration
// Preset easing
linear, ease, ease-in, ease-out, ease-in-out, step-start, step-end
easing-[{value}] // Custom easing: easing-[cubic-bezier(0.4,0,0.2,1)]
// Other custom possibilities
linear(0, 0.25, 1)
steps(4, end)
Split Animations
Animate text parts individually
// Split by words
split-word
// Split by letters
split-letter
// Split by child items
split-item
// Split with custom animation
split-fade-r, split-fade-u, split-zoomin
// Split delay in milliseconds
split-delay-50, split-delay-100
// Split delay with stagger types
split-delay-50-linear
split-delay-50-center // X/Y axes
split-delay-50-edges // X/Y axes
split-delay-50-random
Count Animation
Animate numbers from 0 to their displayed value
// Syntax: count-[target]
// The target number must exist in the element's text content
count-[1234] // Element must contain "1234"
count-[98.5] // Element must contain "98.5"
count-[42,350] // Element must contain "42,350"
// HTML Example:
<span data-usal="count-[1000]">1000</span>
// Animates: 0 → 1000
<span data-usal="count-[4.9]">Rating: 4.9/5</span>
// Finds "4.9" and animates: 0 → 4.9
Modifiers
Animation behavior modifiers
// Duration in milliseconds
duration-500, duration-1000, duration-2000
// Delay in milliseconds
delay-200, delay-500, delay-1000
// Threshold (percentage of element visible)
threshold-10, threshold-30, threshold-50
// Blur effect
blur // Default blur (0.625rem)
blur-2 // Custom blur (2rem)
// Behavior modifiers
once // Run animation only once
loop / loop-{mirror/jump} // Run animation continuously (overrides 'once') (default: mirror)
forwards // Keep final animation state
Text Effects
Special character animations (requires split-letter)
// Effects available:
text-shimmer // Shimmering light sweep
text-fluid // Morphing font weight
// Must be used with split-letter:
<h1 data-usal="split-letter text-shimmer">Text</h1>
// For best results:
// - Use duration-2000 to duration-3000
// - Use split-delay-50 to split-delay-100
<h1 data-usal="split-letter text-shimmer duration-2000 split-delay-75">
Beautiful
</h1>
Tip: For large elements (>100vh), use lower thresholds like
threshold-5 or threshold-10 to ensure animations trigger properly.
Advanced
data-usal-id
Unique identifiers control animation reactivity
<!-- Automatic ID (re-animates when element is recreated) -->
<div data-usal="fade-u">Auto ID</div>
<!-- Custom ID (prevents re-animation) -->
<div data-usal="fade-u" data-usal-id="unique-element">Fixed ID</div>
Animation Tuning
Fine-tune animations with numeric values
// Syntax: {animation}-{direction}-{value1}-{value2}-{value3}
// Fade: controls movement distance
fade-40 // 40% movement (default: 15% normal, 50% split(word/letter))
fade-30-60 // X: 30%, Y: 60%
// Zoom: controls scale and movement
zoomin-30 // 30% intensity (default: 15% normal, 50% split(word/letter))
zoomin-40-60 // movement: 40%, intensity: 60%
zoomin-30-50-80 // X: 30%, Y: 50%, intensity: 80%
// Flip: controls rotation and perspective
flip-120 // 120° angle (default: 90°)
flip-90-60 // angle: 90°, perspective: 60rem (default: 25rem)
Custom Timeline
Create precise keyframe animations
// Syntax: line-[{timeline}]
// Case-insensitive, spaces/linebreaks allowed for readability
// PROPERTIES REFERENCE:
// o±value = Opacity (0-100, auto-clamped to 0-1)
// s±value = Scale (X and Y axes)
// sx/sy/sz = Scale individual axes
// t±value = Translate (X only, %)
// tx/ty/tz = Translate individual axes (%)
// r±value = Rotate (Z-axis, degrees)
// rx/ry/rz = Rotate individual axes (degrees)
// b±value = Blur (rem, negatives become 0)
// g±value = Glow (brightness) (%, negatives become 0)
// w±value = Weight (font weight) (100-900, negatives become 0)
// p±value = Perspective (rem)
// KEYFRAME BEHAVIORS:
// No pipes: FROM value TO original state
line-[o+0] // Opacity: 0 → 1 (original)
line-[s+0.5] // Scale: 0.5 → 1 (original)
line-[o+0 s+0.3 tx+50] // Multiple: all start from values → original
// One pipe: FROM original TO value
line-[|o+50] // Opacity: 1 → 0.5
line-[|60s+1.5] // At 60%: scale to 1.5, holds to 100%
line-[|o+20 s+1.5] // Multiple: original → final values
// Multiple pipes (2+): Copies first to 0%, last to 100%
line-[|25o+30|50o+60|75o+90]
// 0%: o+30, 25%: o+30, 50%: o+60, 75%: o+90, 100%: o+90
// COMPLEX EXAMPLES:
// Multi-property animation
line-[o+0 s+0.5 tx+50 | 50 s+1.2 tx+25 | o+100 s+1 tx+0]
// 0%: opacity 0, scale 0.5, translateX 50%
// 50%: scale 1.2, translateX 25%
// 100%: opacity 1, scale 1, translateX 0
// 3D transformations (order matters!)
line-[rx+90 p+50] // Flip X with perspective
line-[p+100 ry+180 sx+0.5] // Perspective first, then rotate & scale
// Multi-line for readability
line-[
o+0 sx+0.2 sy+0.2 tx+70 |
40 o+50 sx+0.8 sy+0.8 tx+35 |
70 o+90 sx+0.95 sy+0.95 tx+5 |
o+100 sx+1 sy+1 tx+0
]
// Fluid Text
line-[w+100|50w+900|w+100] loop linear split-letter
// Shimmer Text
line-[o+50g+100|50o+100g+130|o+50g+100] loop linear split-letter
// Combine with modifiers
line-[o+0s+0.5] duration-2000 forwards
line-[|50s+1.5] loop
line-[rx+360] easing-[cubic-bezier(0.4,0,0.2,1)]
// IMPORTANT NOTES:
// - Transform order affects result (rotate then translate ≠ translate then rotate)
// - Don't mix general & specific (s+0.5 with sx+0.8 = conflict)
// - Duplicate properties: last value wins
// - Overrides standard animations (fade, zoomin, etc.)
JavaScript API
window.USAL.config(options)
Configure or reconfigure at any time
// Initial configuration
window.USAL.config({
defaults: {
animation: "fade", // Default animation type
direction: "u", // Direction (u, d, l, r, ul, ur, dl, dr)
duration: 1000, // Animation duration (ms)
delay: 0, // Animation delay (ms)
threshold: 10, // Viewport threshold (%)
splitDelay: 30, // Delay between split items (ms)
forwards: false, // Keep final animation state
easing: "ease-out", // CSS easing function
blur: false // false, true (0.625rem), or number (rem)
loop: 'mirror', // Default loop type (mirror, jump)
},
observersDelay: 50, // Delay for observers (ms)
once: false // Run animation only once
});
// Can be reconfigured later
window.USAL.config({
defaults: {
duration: 2000, // Change duration
easing: "ease-in-out" // Change easing
},
once: true // Now runs only once
});
window.USAL.destroy()
Completely shut down USAL, remove all observers and restore original state
// Completely shut down USAL
window.USAL.destroy();
// After destroy, USAL is turned off completely
// Elements return to their original state
window.USAL.restart()
Destroy and reinitialize USAL (equivalent to destroy + init)
// Restart USAL completely
window.USAL.restart();
// This is the same as calling:
// window.USAL.destroy() then reinitializing
Properties
Read-only properties
// Check if initialized
console.log(window.USAL.initialized()); // returns boolean
// Get version
console.log(window.USAL.version); // string
Framework Comparison
Changelog
v1.3.1
October 2025Bug Fixes & Stability
-
Blazor Component Reuse Compatibility: Fixed edge case where
Blazor's DOM element reuse caused animations to trigger on elements without
data-usal
attributes-
USAL now ignores elements with empty or whitespace-only
data-usal
values - Prevents animations from triggering when Blazor strips attributes during navigation
- Special thanks to @mdmontesinos for the detailed investigation and testing (Issue #5)
-
USAL now ignores elements with empty or whitespace-only
-
Tab Visibility Desynchronization: Fixed stagger timing
issues when browser tabs become inactive
- Implemented virtual time system capped at 16.67ms per frame
- Stagger delays now remain synchronized after tab switches
- Eliminates animation "bunching" when returning to inactive tabs
- Applies to both split animations and count animations
-
Console Error Handling: Improved error handling for
replaceChild
operations during DOM manipulation- Elements causing errors are now properly ignored and cleaned up
- No visual impact on animations
- Reduces console spam in edge cases
-
Split Animation Nesting: Fixed excessive DOM nesting in
split letter/word animations
- Proper instance detection prevents duplicate wrapper generation
- Cleaner DOM structure with minimal nesting
v1.3.0
September 2025Brand Identity & Visual Refresh
- New Logo System: Complete rebrand with automatic dark/light theme switching
- Full icon set in multiple formats (PNG, WebP, GIF) and sizes (16px to 512px)
- Updated favicon and social media assets for better brand consistency
SSR & Framework Compatibility
- Complete SSR Overhaul: HTML structure now fully preserved during animations
- No element cloning or reconstruction - only text nodes modified for text/count animations
-
Angular Fix: Migrated to standalone directive with proper
usal="value"
syntax - Added Vue.js plugin setup alongside existing Nuxt configuration
- Eliminated hydration mismatches across all SSR frameworks
Animation Engine Improvements
- Fixed 5-95% Progress Bug: Animations now use full 0-100% range, eliminating visual artifacts
-
Text Effects via Timeline:
text-shimmer
andtext-fluid
now use timeline syntax- Shimmer:
line-[o+50g+100|50o+100g+130|o+50g+100]
- Fluid:
line-[w+100|50w+900|w+100]
- Shimmer:
-
New timeline properties:
g±value
(glow/brightness) andw±value
(font weight morphing) - Better synchronization with eliminated "snap" effect at animation boundaries
Testing & Documentation
- Comprehensive Test Suites: Added integration tests for all major frameworks
- Framework usage examples moved directly below installation for better discoverability
- Updated API documentation with new timeline properties and examples
Robustness & Performance
- Added processing locks to prevent race conditions during DOM mutations
-
Configuration System: Improved
config.defaults
inheritance and cascading - Optimized DOM manipulation with RequestAnimationFrame batching
- Enhanced memory management and cleanup with proper promise chains
v1.2.3
September 2025New Slide Animation
- Pure movement animation with
slide
- no opacity changes -
All directional variants supported (
slide-u
,slide-d
,slide-l
,slide-r
, etc.) - Maintains original element opacity throughout animation
- Perfect for elements that need to stay fully visible during entrance
Critical Bug Fixes
- HTML Preservation: Split and count animations now correctly preserve HTML structure (bold, italic, nested elements)
- Animation Persistence: Fixed parser bug that was resetting all animations to fade
-
Split Tuning: Tuning parameters like
fade-u-200
now work correctly with split animations - Letter Animation: Fixed inline-block display and "snap" effect after animation completion
Performance & Compatibility
- Improved animation cleanup with better garbage collection hints
- More efficient memory management for long-running applications
- Fixed Node.js version compatibility issues in build script
- Works seamlessly across Node.js versions 18-24
Internal Improvements
-
Refactored configuration generation with
genEmptyConfig()
- Clearer variable naming throughout the codebase
- Better separation of concerns in animation setup functions
- Enhanced recursive processing for DOM manipulation
v1.2.2
September 2025Advanced Loop Controls
-
New
loop-mirror
andloop-jump
modifiers for different loop behaviors -
Back and forth animations with
loop-mirror
(default behavior) - Restart animations from beginning with
loop-jump
- Configurable default loop type via
config.defaults.loop
Enhanced Split Animation Stagger
-
Linear distance-based stagger with
split-delay-{value}-linear
-
Center-outward stagger on X/Y axes with
split-delay-{value}-center
-
Edges-inward stagger on X/Y axes with
split-delay-{value}-edges
- Random stagger pattern with
split-delay-{value}-random
Precision & Performance
-
Support for decimal blur values (e.g.,
blur-0.5
,blur-1.5
) -
Added
ease-in-out
,step-start
, andstep-end
easing functions - Refactored animation controller with better state management
- Improved parsing for split animations with tuning parameters
Playground Enhancements
- Enhanced controls with loop type selector and stagger pattern options
- New preset examples demonstrating stagger effects
- Updated debug panel with loop and stagger test cases
v1.2.1
September 2025Bug Fixes
-
Fixed animation tuning values parsing when no direction is specified (e.g.,
fade-50
,flip-90
now work correctly)
v1.2.0
September 2025Advanced Animation Features
-
Loop animations: New
loop
modifier for continuous animation cycles -
Custom timeline animations: Advanced
line-[{timeline}]
syntax for precise keyframe control - Animation tuning: Numeric parameters for fine-tuning standard animations (fade-40, zoomin-30-50, flip-120-60)
-
Enhanced blur effects: Custom blur intensity with
blur-{value}
syntax -
Forwards modifier:
forwards
option to maintain final animation state
Timeline System
- Support for opacity, scale, translate, rotate, blur, and perspective properties
- Multi-keyframe animations with percentage-based timing
- 3D transformations with proper transform order handling
- Case-insensitive syntax with multi-line support for readability
Technical Updates
- Bundle size updated to ~8KB gzipped (from ~5KB) due to advanced features
- Enhanced debug panel with comprehensive testing suite
- Improved element cleanup and animation state handling
v1.1.1
September 2025Robustness & Stability
- Complete API overhaul with promise-based chain system (Rust-like enum states)
- Significantly improved edge case handling for extreme usage scenarios
- Better orchestrated state machine for animation lifecycle
- Enhanced public API usability and consistency
Bug Fixes
- Fixed text animations losing characteristics in flex containers without wrapper
- Resolved smooth scroll issue on mobile when switching tabs
Developer Experience
- Added comprehensive debug panel in source code for development testing
- Improved error handling and recovery mechanisms
v1.1.0
September 2025Complete WAAPI Migration
- Full migration to Web Animations API (WAAPI) for all animations
- Only count animations remain using RAF for precise number formatting
- Zero direct DOM manipulation - no inline styles or attributes
Framework Compatibility
- Eliminated SSR hydration mismatches
- No longer manipulates HTML node attributes directly
- Perfect compatibility with React, Vue, Svelte, Solid, and other SSR frameworks
Performance
- Significantly improved performance through WAAPI
- Better browser optimization and hardware acceleration
- Reduced JavaScript overhead
v1.0.0
September 2025Initial Release
- 40+ scroll-triggered animations
- Intersection Observer based triggers
- Zero dependencies, ~5KB gzipped
- Full Shadow DOM support
- Framework packages for all major libraries
- Automatic initialization
- Split text animations (word/letter)
- Count animations with smart formatting