Skip to content
animorio
IT
All components

The vacuum-sealed chat

Chat, Physics, SVG, Transitions, TextGSAPAdvancedCompressed weight: Demo 17 KBGSAP 28 KBFreeDownload the zip

The group chat of three flatmates, where the field you write in is a grey skin made of points on springs. Sending, the skin lets go of the field and closes round the words like a vacuum pack, turns yellow and carries them up into the conversation, stretching on the way and wobbling as it lands; a new one comes out of the button. The answer is written in a ghost bubble in the colour of whoever is writing: instead of words, bars as long as the words grow in bursts, now and then taken back, and when it is sent the bubble fills and the bars develop into the words.

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 vacuum-sealed chat</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/rubik@5.3.0/index.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the group chat of a shared flat: the field's grey skin closes round the words like a vacuum pack and carries
         them up into the conversation, and a flatmate's answer is written in a ghost bubble that shows the shape of
         its words before the words themselves -->
    <section class="chat" aria-label="Flat 4B, group chat" data-chat data-autoclic>
      <header class="chat__head">
        <div class="chat__faces" aria-hidden="true">
          <span class="face" style="--who: var(--who-1)">I</span>
          <span class="face" style="--who: var(--who-2)">T</span>
        </div>
        <div class="chat__title">
          <h2 class="chat__name">Flat 4B</h2>
          <p class="chat__status" aria-hidden="true" data-status><span class="status__line">Iris, Theo and you</span></p>
        </div>
      </header>

      <div class="chat__thread" data-thread>
        <div class="chat__log" role="log" aria-live="polite" aria-label="Messages" data-log></div>
      </div>

      <form class="chat__composer" data-form>
        <div class="field" data-field>
          <label class="sr" for="chat-input">Message Flat 4B</label>
          <textarea class="field__input" id="chat-input" rows="1" placeholder="Message Flat 4B" maxlength="280" enterkeyhint="send" aria-describedby="chat-keys" data-input></textarea>
          <span class="sr" id="chat-keys">Enter sends, Shift and Enter starts a new line.</span>
          <span class="field__caret" aria-hidden="true" data-caret></span>
          <button class="send" type="submit" aria-label="Send" disabled data-send>
            <span class="send__bg" aria-hidden="true"></span>
            <svg class="send__arrow" viewBox="0 0 24 24" aria-hidden="true" data-arrow><path d="M12 18.6V5.8M6.6 11.2 12 5.8l5.4 5.4"/></svg>
          </button>
        </div>
      </form>

      <!-- the skins that move between the field and the conversation, and the words they carry -->
      <svg class="chat__skins" aria-hidden="true" data-skins></svg>
      <div class="chat__fly" aria-hidden="true" data-fly></div>
    </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.

48 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 vacuum-sealed chat” from Animorio and fit it into my site

What it does

On a white background a chat 384 pixels wide: at the top the two circles with initials, Flat 4B and Iris, Theo and you; under it, four messages (Iris and Theo on the left in grey, with the name in colour over the first of each run, mine on the right in yellow) and at the bottom the field Message Flat 4B, a grey pill 56 pixels tall with the round send button. Tapping the field draws a grey edge round it; typing, the button’s black ground swells in with a bounce and the arrow turns yellow, and when the words wrap the field grows upwards, up to five lines, its top edge wobbling a moment while the messages move up to make room. Sending (Enter, or the button; Shift and Enter starts a new line) the button goes down and back up, the arrow shoots off and a grey one rises, and the field’s grey skin lets go and closes round the words, turning yellow: for an instant the empty field holds only the vacuum-sealed message. Then it rises and slides to its place at the bottom of the conversation, stretching the way it goes, with the words inside keeping their lines; landing it squashes and wobbles, and the messages above, which had already moved up, get a small push. Meanwhile a grey drop comes out of the button and spreads into the new field. Soon under my message the bubble of whoever answers appears: only an outline in their colour, with their name over it, and instead of the line under Flat 4B it says Theo is typing. Inside it light blue bars as long as the words being written grow in bursts, with a caret after the last one: now and then a word grows, stops, is deleted and written again, and a word that no longer fits slides down to the next line while the bubble grows. Once the answer is sent the bubble fills with grey with a small jolt, the bars fade and the words come into focus in their place one after the other. Depending on what you write, Theo (in blue) or Iris (in raspberry) answers.

How it works

A skin of points on springs

Every shape that moves is a ring of 62 points always laid in the same order on a rounded box (seven on every corner, twelve along the long sides, five along the short ones): so a corner of the field always becomes a corner of the bubble. Every point is pulled to its place by a damped spring, x'' = −k(x − goal) − c·x', in steps of 1/240 of a second with the frame’s step limited to 1/30; its two neighbours drag it, but on how far each is from its place and not on where it is, so the skin rests exactly on the shape and only its waves are smoothed out. Moving, it is drawn as a closed Catmull-Rom curve; still, with exact arcs, and it hands over to the real bubble, an element with a border-radius. When nothing moves the loop stops.

Vacuum-sealed

On sending, a hidden copy of the textarea, with the same measures and a span for every word, says where every word is; the words move into a layer above everything, in the same spot, in the same frame in which the textarea empties. The field’s skin takes as its goal the box of the words plus the bubble’s padding, on springs 1.9 times stiffer with a damping of 0.72, while its colour goes from grey to yellow: in WRAP seconds the field has closed round the message.

The way up

The real message is already in its place in the conversation, see-through. After WRAP the skin’s goal is a box between the one in the field and the real bubble: x and width follow power3.inOut, y and height power3.out, so it rises first and slides across after, in LIFT seconds. The points at the front, in the direction of travel, have stiffer springs than the ones behind (up to 35% more or less, from the product of the point’s normal and the direction): the skin stretches as it goes and squashes as it stops. A new skin comes out of the button, from a 4 pixel dot, and spreads into the field.

The words stay inside

The words have no path of their own: each keeps a fixed distance from the middle of the shape, going from the one in the field to the one in the bubble, and they all move by as much as the skin’s centroid trails behind its goal. So they never leave the skin, they wobble with it and they are exactly in place when it is still. The bubble is at most as wide as the field’s lines (max-width: calc(100% − 50px)), so the line breaks stay the same and the words travel as one block.

The conversation makes room

Before every change each message’s place is read; after it, each starts again from its old place on a spring, the lower ones a little stiffer, so the push travels up the conversation. When the bubble lands, the messages above get an upward push of BUMP pixels per second, 30% less for each one further up. The oldest fade out under the header (a mask at the top) and past fourteen they go.

The draft and the developing

The draft is a real bubble with the words written so far in a see-through ink: they are measured with getClientRects and never seen. On every word a bar as long as the word, a caret after the last one, and round it a skin outlined in the writer’s colour that follows the bubble as it grows; a word that wraps slides its bar down to the next line. The way of writing is made up once per answer by a generator seeded from the text: bursts at TYPING letters per second, pauses after commas and full stops, a word written and deleted. Once the answer is sent, the real message takes the draft’s place and its skin; the colour goes to solid grey mixed with premultiplied alpha, a push from the middle swells it for a moment, the bars fade and the words come in from blur(5px). The conversation is a role log with aria-live polite, every message starts with who speaks (hidden from sight) and the draft is aria-hidden; a click from a script, the library’s preview, writes a message letter by letter without taking the focus and sends it.

Values to change

NameFileDefaultWhat it changes
WRAPscript.js0.16The seconds the field’s skin takes to close round the words before it lifts.
LIFTscript.js0.5The seconds of the message’s way from the field to its place in the conversation.
SPRINGscript.js[420, 0.5]The skin’s springs: how stiff, and how damped, from 0 (wobbles for ever) to 1 (no wobble).
TENSIONscript.js0.5How much each point of the skin drags its neighbours along, from 0 to 1: higher, smoother.
TYPINGscript.js22The letters per second of a flatmate writing an answer.
WAITscript.js0.6The seconds between the message landing and the answer starting.
BUMPscript.js130How hard a message that lands pushes the ones above it, in pixels per second.
PEOPLEscript.js['Iris', 'Theo']The flatmates, in the colours --who-1 and --who-2.
REPLIESscript.js[['milk', 'Theo', 'Guilty. Two cartons on my way home, promise.'], …]A word to look for in my message, who answers it, and the answer.
OTHERscript.js[['Iris', 'Ha, fair. Let’s talk about it tonight.'], …]The answers to anything else, one after the other.
DEMOscript.js['Who finished the oat milk?', …]What the library’s preview writes and sends, one message per click.

Try them in the Playground

When to use it

For the chat of a group or flatmates app, a shop’s support, an assistant that answers, live comments, the demo of a messaging app: wherever the field you write in and the moment of sending should have a character of their own.

On phones

The chat takes the width of the screen less the margins (358 pixels at 390) and the bubble stays as wide as the field’s lines; the textarea is 16 pixels, so Safari on iPhone does not zoom the page, the keyboard shows its send key (enterkeyhint send) and the button is a 44 pixel target that goes down under the finger.

With reduced motion

No skin closing or travelling: the message appears in its place fading in and the field stays as it is; the messages do not slide, the draft grows word by word without the bars moving and the answer appears fading in.

Pitfalls

  • Words that follow their own path while the skin trails on its springs leave the skin (here the last word stuck out on the right): they must be carried by the skin, at a fixed distance from the shape’s middle plus the lag of its centroid.
  • Starting the words one after the other (stagger) while they all go right makes the first ones catch up with the last: they pile up. They travel together.
  • If the bubble is narrower than the field, the words change line on the way and run over the others: the bubble is at most as wide as the field’s lines, and a word that changes line anyway fades out and back in instead of crossing.
  • Neighbours pulling on the position, instead of on the distance from their place, round the corners off and the skin no longer rests on the bubble; and a stiffness that differs from point to point by more than 5% makes the edge of a skin that closes fast look jagged.
  • Mixing a nearly see-through blue with a solid grey channel by channel takes the bubble through a strong blue: mix with premultiplied alpha.
  • A button disabled while it has the focus loses it, and someone on the keys finds themselves on the page: after sending from the button the focus goes back to the field. And a CSS transition on the button’s transform delays the scale GSAP writes: the press is done by GSAP alone.

Libraries

GSAP 3.15.0, Rubik (font variabile) 5.3.0.

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

Testing

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