Skip to content
animorio
IT
All components

The cassette player

Players, Physics, SVGGSAP, CanvasAdvancedCompressed weight: Demo 20 KBGSAP 28 KBFreeDownload the zip

A music player with a clear cassette seen close up, and no sound: the tape is the player. Smoked plastic with its reflections, screws, a label written by hand; as it plays the reels turn, the brown tape runs from one to the other and the reel that empties spins faster and faster, as the physics of the radii says. Forward and back wind the tape at full speed to the song, with the reels blurred by the motion. Turning a reel with a finger goes forward and back, and the tape goes slack in a loop that hangs and then pulls tight again.

The code

A folder that runs on its own: index.html, style.css, script.js and the libraries from a CDN. The React and Vue tabs hold the same demo as a ready component, which switches its animations off when it leaves the page.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>The cassette player</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/instrument-sans@5.3.0/wdth.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/caveat@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the cassette player: a music player whose top is a clear smoked cassette seen close up, sitting on the deck.
         The reels, the tape and the deck's head are painted on a canvas every frame; the shell, the label and the
         screws are SVG. The packs of tape grow and shrink with the time, a reel turned by hand pulls the tape or lets
         it go slack, and the keys go down like a deck's piano keys. No sound -->
    <section class="player" aria-label="Cassette player" data-player>
      <div class="deck" data-deck>
        <svg class="slot" viewBox="0 0 352 252" aria-hidden="true" data-slot></svg>
        <svg class="shell" viewBox="0 0 352 252" aria-hidden="true" data-back></svg>
        <canvas class="inside" aria-hidden="true" data-inside></canvas>
        <svg class="shell" viewBox="0 0 352 252" aria-hidden="true" data-front></svg>
        <div class="grip" role="slider" tabindex="0" aria-label="Position on side A" aria-valuemin="0" data-grip></div>
      </div>

      <ol class="card" aria-label="Side A" data-list></ol>

      <div class="times" aria-hidden="true"><span data-at>0:00</span><span data-left>0:00</span></div>

      <div class="keys">
        <button class="key" type="button" aria-label="Previous song" data-key="rew"><span class="key__body"><span class="key__cap"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M11.5 6.5v11L4 12zM20 6.5v11L12.5 12z"/></svg></span></span></button>
        <button class="key key--play" type="button" aria-label="Play" aria-pressed="false" data-key="play" data-autoclic><span class="key__body"><span class="key__cap"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M8 5.5v13l10.5-6.5z"/></svg></span></span></button>
        <button class="key" type="button" aria-label="Pause" data-key="pause"><span class="key__body"><span class="key__cap"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M7 6h3.6v12H7zM13.4 6H17v12h-3.6z"/></svg></span></span></button>
        <button class="key" type="button" aria-label="Next song" data-key="ff"><span class="key__body"><span class="key__cap"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12.5 6.5v11L20 12zM4 6.5v11L11.5 12z"/></svg></span></span></button>
      </div>
      <p class="sr" 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.

34 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 cassette player” from Animorio and fit it into my site

What it does

On a white background a brushed aluminium player 380 pixels wide. At the top a clear smoked plastic cassette, seen close up: inside you see the two packs of brown tape on their white toothed hubs, the deck's spindle in each hub, the rollers in the corners, the felt pad on its bronze spring and the tape running from the left pack along the bottom edge to the right one. On top is the paper label with Aubade's orange stripe, the side's A and the title written in pen, Long drive, June; the plastic has the window, the raised frame, the matte grain, five screws and two reflections that move with the pointer. Under the cassette, in the player's slot, the chrome head, the capstan and the pinch roller. Then the card of side A written by hand on ruled paper (five songs and their lengths), the time gone and the time left, and four piano keys: back, an orange play, pause and forward. Pressing play the key stays down, the head and the roller rise into the cassette and press the tape onto the felt, and the tape starts to run: first the clear leader goes by, then the white splice and the brown tape, and the music starts. The reels turn at different speeds: the full one slowly, the empty one fast, and as the side goes on they swap. Forward and back press their key down, pull the head away and send the tape at full speed to the next song or to the start of the one playing: the reels blur, the one filling up grows before your eyes, and once there the key springs back up and the music starts again. Pause makes the play key jump up and the head goes down. A click on a song on the card goes there with the fast tape and plays it; the song playing is written bolder and a yellow highlighter covers it as it goes by. The reels can be taken with the mouse or a finger: turning a reel the way that winds, the tape pulls tight, runs past the head (the time goes forward or back) and drags the other reel along, which left to itself turns on a little and leaves a loop of tape; turning it the other way the tape goes slack and hangs in a loop that bumps into the packs and the plastic, until the clutch winds it in and pulls it tight.

How it works

The time is the tape

Everything comes from one number, x, the seconds of tape gone past the head. The side is the clear leader, the songs with three seconds of silence between them and some blank tape at the end; the tape runs at 4.76 centimetres a second, which in the drawing is 167 units. The song's time, the song playing, the highlighter and where the splice between leader and brown tape is all come from x.

The radii of the reels

A pack is wound tape, so its area grows with its length: r² = r₀² + k·L, with r₀ the hub and k chosen so the whole tape makes a full pack of 79 units. The left pack holds the tape still to come, the right one the tape gone by. Since the tape goes past the head always at the same speed, each hub turns at v / r: at the start the empty one turns more than twice as fast as the full one, and by the end of the side they have swapped. The free tape runs along the outer tangents between the packs and the rollers, worked out again every frame.

The slack in the tape

Each free span, from a pack to its roller, knows how much more tape it has than the straight line. A reel that lets tape go adds it to its span; one that winds first takes the slack and then, with its span tight, pulls the tape past the head (x changes) and takes slack from the other span, which when it goes below zero drags the other reel. The reels have a speed of their own that friction kills in COAST seconds: when the pull stops short they keep turning and make a loop. Then the clutch winds it in over TENSION seconds.

The loop that hangs

A slack span is a Verlet chain of 26 points between its two ends, as long as the straight line plus the slack: weight, 16 passes of length constraints, two stiffness constraints (two and three links apart never get closer than a smallest curl: the tape curls, it does not zigzag), and collisions with the packs, the rollers and the edges of the plastic. When the slack dies away the tension pulls it straight quickly and the chain rests. It is drawn as a Catmull-Rom curve.

The blurred hub

The hub with its teeth, the tape clamp and the deck's spindle are drawn once and turned; the light on the hub and on the pack does not turn. In the fast wind a hub can turn twice in one frame and at 60 frames a second it would look still or backwards: it is drawn several times across the angle it turned in that frame, with opacity 1, 1/2, 1/3… which gives the right average, that is real motion blur.

Keys, head and preview

The keys are real buttons with a CSS variable --press that GSAP takes from 0 to 1: the cap goes down and its front face gets shorter; play stays down at 0.8 with aria-pressed, forward and back stay down until the tape gets there and come back up with an elastic bounce. The head and the roller rise in 0.24 seconds, then the capstan picks up speed. The cassette is a role slider (arrows 5 seconds, pages 30), Space plays and pauses. A click from a script on play (the library's preview) does the round: it plays, goes to the next song, lets the tape go slack and tightens it, goes back with the left reel, pauses and rewinds.

Values to change

NameFileDefaultWhat it changes
SIDEscript.js[['Cinder Blue', '3:41'], ['Coastline FM', '4:05'], …]The songs on side A, written on the card: the title and how long it lasts (minutes:seconds). The tape and the packs are made to this length.
TITLEscript.jsLong drive, JuneThe title written by hand on the cassette's label.
BRANDscript.jsAUBADEThe tape's make, printed on the label's stripe.
WINDscript.js1.5The seconds the fast wind takes to get to the next song.
SLACKscript.js1How loose the tape can get when a reel is turned by hand: 0 never, 2 a lot.
WEIGHTscript.js420How heavily a loose loop of tape hangs, in pixels a second squared.
TENSIONscript.js0.32The seconds the reels take to pull a loose loop tight again.
COASTscript.js0.14How long a reel spins on by itself when the tape stops pulling it, in seconds.
LIGHTscript.js1How far the pointer moves the reflections on the plastic (0 = not at all).

Try them in the Playground

When to use it

For the site of a musician or an independent label, the page of a cassette reissue, a playlist made like an old mixtape, a radio station or a podcast with an analogue feel: a player that looks like a real object to hold.

On phones

The player is at most 380 pixels wide and the cassette narrows with it (at 390 it is 330 wide): the canvas is painted again at the right size. A finger takes a reel (the left or right half of the cassette) and turns it without scrolling the page: touch-action none only on the cassette. The keys are a quarter of the row wide and 46 pixels tall, the card's lines 30. The reflections follow the finger while it touches the player.

With reduced motion

The reels do not turn and the tape never goes slack: the packs only change size with the time, which runs all the same while it plays. The fast wind gets there in a moment with a fade, the keys and the head change position at once, the player fades in without the cassette dropping in. Turning a reel by hand still moves the time, forward or back.

Pitfalls

  • In the fast wind the hubs turn more than once a frame, and drawn once they looked still or turned backwards: now they are drawn several times across the angle turned in that frame, with opacities 1, 1/2, 1/3… that make a fair average.
  • Even at normal speed the empty hub, drawn twice, looked out of focus: the blur only starts above 0.11 radians a frame.
  • The tape's chain, with all the slack taken up, never got straight again: its weight always made it sag a little and the constraints alone were not enough. Now the weight grows with the slack (a nearly tight tape is held up by its tension) and under 5 units of slack the points are pulled towards the straight line.
  • The collisions with the packs had a one pixel margin, and the line tangent to the pack was always inside it: the points were pushed out and the chain never rested. The collisions are now at the exact radius.
  • A span going slack all at once folded into a zigzag: now as soon as it goes slack it takes a slight bow to one side, and two stiffness constraints (two and three links) never let the tape bend more than a curl.
  • The dragged reel stopped turning together with the pull, because its speed followed the tape even when slowing down: now only friction takes its speed away, and the tape can only speed it up, so when the pull stops short the reel runs on and makes a loop.
  • Holding a reel in the hand while it played, the motor turned it under the fingers all the same: now the tape coming to a held reel piles up loose in front of it, up to the most slack.
  • The tangent's angle on the left roller sometimes came out as −180° instead of +180°, and the tape's arc went round the wrong side of the roller: the angle is brought between 0 and 360 before drawing.

Libraries

GSAP 3.15.0, Instrument Sans (font variabile) 5.3.0, Caveat (font variabile) 5.3.0.

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

Testing

Tested on 17 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