Animations

Canvas and WebGL effects that stop rendering the moment they leave the screen.

Using the catalog 2 min read

TL;DR · to infinity, but only on screen

  • Each animation is one vs-anim-*.js file that paints into its own canvas.
  • It only renders while visible: off screen or in a background tab, the loop is cancelled.
  • Under reduced motion it draws one good still frame and never starts.

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:

01
On screenThe host element intersects the viewport
02
Tab visibleThe page is the one you're looking at
The loop, the second you scroll past it.

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

Reduced motion on: one calm frame, nothing looping.

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:

  1. Keep the loop gate. Replace it with a bare requestAnimationFrame and the effect runs forever, off screen, in a background tab.
  2. Keep the reduced-motion branch. It's the difference between "beautiful" and "unusable" for a real slice of your users.

Heavy items

A fluid sim and a particle field in the same viewport.

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. Two heavy ones in the same viewport is that GIF, in real life.