Animations
Canvas and WebGL effects that stop rendering the moment they leave the screen.
Animations are self-contained Web Components that paint into their own canvas:
text effects, pointer trails, scroll reveals, WebGL backgrounds, 3D scenes. One
vs-anim-*.js file each, zero dependencies, usable in any framework. Browse them
at Animations.
Categories: Interactive, Text, Scroll, Background, Loop, WebGL, 3D, Pointer.
Using one
Same as any component — import the file once to register the tag, drop it, pass props as attributes:
<script type="module">import './vs-anim-shiny-text.js';</script>
<vs-anim-shiny-text text="Now in beta" speed="1.2" color="#a78bfa"></vs-anim-shiny-text>
Background animations expect a sized parent. Give them one:
<div class="hero">
<vs-anim-grid-glow class="hero__bg"></vs-anim-grid-glow>
<h1>Ship faster</h1>
</div>
<style>
.hero { position: relative; min-height: 60vh; }
.hero__bg { position: absolute; inset: 0; z-index: 0; }
.hero h1 { position: relative; z-index: 1; }
</style>
They stop when you aren’t looking
Every animation drives its frames through the same gated loop. It runs only while both are true:
- the host element is intersecting the viewport, and
- the tab is visible.
Scroll past it and the rAF is cancelled — not throttled, cancelled. Switch tabs and it stops. The delta time is clamped, so coming back after a minute away resumes smoothly instead of teleporting the state forward.
That is why a page can carry several of these without melting a laptop.
Reduced motion
Every animation checks reduced motion at mount, and honors two signals:
- the OS setting (
prefers-reduced-motion: reduce), and <html data-motion="static">, which the catalog uses to freeze previews.
The treatment is never “play it anyway, slower”. It renders one meaningful static frame — the trail frozen mid-curve, the gradient at rest — and never starts a loop.
<!-- freeze every Vuesax animation on the page -->
<html data-motion="static">
Performance notes
Device pixel ratio is clamped to [1, 2]. A 3× phone screen renders at 2×,
which is visually identical and roughly half the fragments.
Teardown is explicit. In disconnectedCallback the loop is cancelled,
observers are disconnected, timers cleared, listeners removed, and WebGL
contexts released through WEBGL_lose_context. Adding and removing an animation
a hundred times does not leak a hundred contexts.
Resize is debounced and reported through one callback, so dragging a window edge does not reallocate buffers on every frame.
When you edit one
Two rules keep the copy well-behaved:
- Keep the loop gate. If you replace it with a bare
requestAnimationFrame, the effect will run forever, off screen, in a background tab. - Keep the reduced-motion branch. It is the difference between “beautiful” and “unusable” for a real slice of your users.
Heavy items
WebGL and 3D animations pull real work onto the GPU — but they still ship as one
zero-dependency file: the effects that used to lean on three or gsap are
rewritten to raw WebGL and hand-rolled tweening, so there is no npm install
line to run. One per page is still a good ceiling; a fluid simulation and a
particle field in the same viewport is not.