Skip to content
animorio
IT
All components

The storage defragmenter

Loading, Physics, TransitionsGSAP, CanvasAdvancedCompressed weight: Demo 11 KBGSAP 28 KBFreeDownload the zip

A phone’s storage being optimized like an old defragmenter: a grid of blocks, one per chunk of a file, with system, apps, photos and files scattered among free gaps and hatched cache. Press Optimize and the cache goes in a wave, then every block is lifted, grows, flies in an arc to its place and lands with a squash, while the one in the way flies the other way. The blocks in place take their full colour and the grid fills like a progress bar, kind after kind. A file that is open will not move and stays up in red until Retry; at the end a ripple crosses the grid, and Optimize again sends every block back where it came from.

The code

A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
  <title>The storage defragmenter</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/martian-mono@5.3.0/wdth.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the storage defragmenter: a phone's storage as a grid of blocks, one per file chunk, scattered with gaps and
         cache. Optimize clears the cache in a wave, then every block is lifted and flown, in an arc, to its place:
         system, apps, photos, documents, and the free space all in one piece at the end. The grid is drawn by script.js -->
    <section class="df" aria-labelledby="df-title" data-defrag>
      <div class="df__head">
        <div class="df__words">
          <h2 class="df__title" id="df-title"><span class="df__swap" data-title>Optimize storage</span></h2>
          <p class="df__meta"><span class="df__swap" data-meta>128 GB · 115 fragments</span></p>
        </div>
        <p class="df__figure" aria-hidden="true">
          <span class="df__pct" data-pct>0%</span>
          <svg class="df__tick" viewBox="0 0 28 28" data-tick><path d="M6.5 14.8l5 5 10-11.4" pathLength="1"/></svg>
        </p>
      </div>

      <div class="df__grid" role="progressbar" aria-labelledby="df-title" aria-valuemin="0" aria-valuemax="100"
           aria-valuenow="0" aria-valuetext="Not started" data-bar>
        <canvas class="df__canvas" aria-hidden="true" data-grid></canvas>
      </div>

      <p class="df__legend" aria-label="What is stored">
        <span class="df__kind" style="--kind: var(--system)"><span>System</span> <b data-size="system">11 GB</b></span>
        <span class="df__kind" style="--kind: var(--apps)"><span>Apps</span> <b data-size="apps">23 GB</b></span>
        <span class="df__kind" style="--kind: var(--photos)"><span>Photos</span> <b data-size="photos">35 GB</b></span>
        <span class="df__kind" style="--kind: var(--docs)"><span>Files</span> <b data-size="docs">15 GB</b></span>
        <span class="df__kind df__kind--cache" style="--kind: var(--cache-ink)"><span>Cache</span> <b data-size="cache">2.4 GB</b></span>
      </p>

      <div class="df__foot">
        <span class="df__status"><span class="df__swap" data-status>Last optimized 3 months ago</span></span>
        <button class="pill" type="button" data-btn data-autoclic>
          <span class="pill__fill" aria-hidden="true"></span>
          <span class="pill__label" data-label>Optimize</span>
        </button>
      </div>
      <p class="sr" role="status" aria-live="polite" data-live></p>
    </section>
  </main>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
  <script src="script.js"></script>
</body>
</html>

Playground

Change values, colours and text with the controls and watch the animation respond live. Or write in the code yourself. Then download the folder with your changes.

53 values to change

Or ask Claude Code for it

With the Animorio connector, Claude Code takes it from here, guide included, and fits it into your project. Paste this request:

Take “The storage defragmenter” from Animorio and fit it into my site

What it does

On a white background, alone in the middle: at the top Optimize storage and under it, small, 128 GB · 115 fragments, with 0% on the right in a narrow monospaced face; below, a grid of 24 by 10 places with round corners, the free places drawn as hairlines and the others filled: black for the system, blue for apps, orange for photos, sand for files, and the cache in diagonal stripes, all mixed up the way years of use leave them. Under the grid the legend in five columns, each name in its kind’s colour with its size in gigabytes, then Last optimized 3 months ago and the black pill Optimize. Coming in, the blocks drop into their places with a small bounce, in a diagonal wave. Press Optimize and the blocks still out of place go pale, a thin frame settles on the first place and the cache goes block by block, shrinking into itself, in the order a page is read, while its size falls to 0 GB and the word Cache is struck through. Then the frame walks the grid: for every place the block that belongs there is lifted, grows by half, makes an arc with its shadow and lands squashing a little and springing back, and the block that was there flies off in the opposite arc. The flights overlap, so a small swarm of blocks is always in the air; the blocks that have arrived take their full colour, so the sorted part grows like a progress bar, black first, then blue, orange and sand, and the fragments counted at the top go down. While it works the pill becomes a ring that says Pause: the frame stops, Resume carries on. One time in two a file is open: its block rises, shakes and stays up with a red edge, the title says Could not move a file in red and under it Notes.app is using it; Retry flies it and the work carries on. At the end the free space is all in one piece at the bottom, a ripple swells the blocks from the top left corner, the percentage gives way to a green tick that draws itself, the title says Storage optimized and at the foot 2.4 GB freed. Optimize again sends every block back to where it started in a great cloud of flights, the cache comes back and everything is as it was.

How it works

A seeded mess

The blocks of each kind (how many is MIX) and the empty places are shuffled with a seeded pseudo-random generator (Park and Miller’s multiplicative one), so the mess is always the same and the preview’s round repeats exactly. Two passes swap a few lonely blocks with a matching neighbour: a real disk’s mess has small clumps, it is not pure noise. Every block remembers where it started, which is where Optimize again sends it.

The plan before the movement

Like a real defragmenter, the work is worked out first on a copy of the grid: the cache is cleared first, then for every place in turn the script checks which kind belongs there (system, apps, photos, files); if the right block is already there it stays, if not the nearest one after that place is taken and the block in the way goes where that one was. Every operation has a weight (a flight 1, a clearing 0.16, a block already in place 0.12) and so its moment in the work, from 0 to 1.

A clock that plays the plan

While it works a clock moves on in GSAP’s ticker (its step is at most 1/30 of a second) and starts every operation as it passes its moment: the percentage is the clock itself. Pause stops the clock and lets the flights in the air finish; the open file is the first flight after FAIL, which holds the clock until Retry lets it go.

The arcing flight

A block flies along the straight line between the two places plus a push to one side, sin(πt) times a height that grows with the distance, and the two blocks of a swap bend to opposite sides, so they cross without covering each other. At the top of the arc the block is bigger by LIFT and has a widening shadow; landing it squashes in width and height with an elastic.out and settles back. Positions live in cells, not pixels, so if the window changes size halfway through a flight the drawing is redone and nothing jumps.

Drawing on the canvas

Everything is one canvas twelve pixels bigger than the grid on every side, so a block in the air is never cut: first the hairline places, then the blocks on the ground, then those in the air from the lowest to the highest, then the frame. The paleness of a block out of place is its transparency, and the cache is hatched through a clip. When nothing moves and the work is still the loop leaves the ticker and draws nothing.

Values to change

NameFileDefaultWhat it changes
GRIDscript.js[24, 10]Columns and rows of the grid of blocks.
MIXscript.js{ system: 18, apps: 38, photos: 62, docs: 30, cache: 28 }How many blocks of each kind; the rest of the grid is free space (at least a fifth stays free).
CACHE_GBscript.js2.4How much the cache that gets cleared weighs.
SECONDSscript.js7How many seconds an optimization takes in the demo.
GAPscript.js2The gap between two blocks, in pixels.
ROUNDscript.js0.22A block’s round corner, as a share of its side.
LIFTscript.js0.55How much a block grows at the top of its flight.
ARCscript.js0.32How high a flight arcs, as a share of its length.
FLIGHTscript.js0.6How long a flight lasts, in seconds.
SEEDscript.js11The starting mess: another number, another scatter.
FAIL_EVERYscript.js2One run in how many meets a file that is open (0 never).
FAILscript.js0.58Where the open file is met, from 0 to 1.
PREVIEWscript.js1.45In the preview’s round: how many times faster.
LABELSscript.js['Optimize', 'Pause', 'Resume', 'Retry', 'Optimize again']The words of the pill, state by state.
--photosstyle.css#ff7442The colour of the photo blocks.
--appsstyle.css#4f56e8The colour of the app blocks.

Try them in the Playground

When to use it

For optimizing or cleaning up storage in a phone or computer app, for an antivirus or a cleaner, for compacting an archive or a database in a dashboard, for any long job that puts lots of small things in order.

On phones

The panel takes the whole width less the margins and the grid shrinks with it: it always has 24 columns, with smaller blocks. The flights are the same, because positions live in cells; the legend stays in five columns. The pill answers a touch as it does the mouse.

With reduced motion

Nothing flies: a block that changes place fades out where it was and in where it goes, the cache fades instead of shrinking, the frame jumps from place to place and there is no final ripple. The full colour of the blocks in place, the percentage and the tick stay.

Pitfalls

  • The list of starting places had the same name as the function that starts the work: the script did not even run. The list is now called scatter.
  • Going back to rest the blocks flew in high arcs that left the canvas and were cut at the top of the grid: now a block in flight stays inside the grid, and the canvas runs twelve pixels past it on every side for the shadow and the growth.
  • The legend on a single line broke halfway and the word Cache ended up alone underneath: now it is five columns with the size under the name, which fit at 390 pixels too.
  • The line striking through Cache ran through its size as well, because text decoration goes down into block children: now the line is on the word alone, in a span of its own.
  • The open file’s block stayed as pale as the blocks out of place and its red edge hardly showed: when it gets stuck it takes its full colour.

Libraries

GSAP 3.15.0, Martian Mono (font variabile, asse wdth) 5.3.0.

Compressed with gzip, the three demo files weigh 11 KB; the libraries: GSAP 28 KB.

Testing

Tested on 19 October 2026

  • Desktop: Done
  • Phone: Done
  • Reduced motion: Done
  • Safari on iPhone: To do

License

Use it in your own projects and in your clients’ projects. Do not resell it or redistribute it as is. GSAP stays under its own Standard license. Full license.

All components