Skip to content
animorio
IT
Back to the library

The sticker that follows the words

Hover, TextGSAP, CSS onlyMediumCompressed weight: Demo 6.5 KBGSAP + SplitText + CustomEase 35 KBFreeDownload the zip

A big quote with two coloured words: point at one and a sticker comes out of it, follows the pointer a little late and leans the way you move it. The face pops up from below with a bounce, the heart opens like a round window and beats.

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 sticker that follows the words</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/inter-tight@5.3.0/index.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/inter-tight@5.3.0/wght-italic.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <!-- a quote from a made-up comedy podcast. Two words are a different colour: on each one a sticker pops out of the
       word and rides along above the pointer, leaning the way it moves; the heart one beats while it is out -->
  <main class="palco">
    <figure class="citazione" data-citazione>
      <blockquote class="citazione__testo" data-testo>
        <p>I tell strangers I <span class="accento" tabindex="0" data-parola="cuore">love</span> them on day one.</p>
        <p>It saves us all a lot of <span class="accento" tabindex="0" data-parola="faccia">awkward</span> time later.</p>
      </blockquote>
      <figcaption class="citazione__firma" data-firma>
        <span class="firma__chi">Rosa Pemberton</span>
        <span class="firma__dove">Late Bloomers, episode 112</span>
      </figcaption>

      <!-- the two stickers: drawn here, never in the flow of the text -->
      <div class="adesivo adesivo--faccia" aria-hidden="true" data-adesivo="faccia">
        <div class="adesivo__corpo" data-corpo>
          <svg viewBox="0 0 220 230" focusable="false">
            <g class="bordo">
              <path d="M112 34c40 0 72 30 72 74 0 50-32 92-72 92s-74-40-74-90c0-44 32-76 74-76Z"/>
              <path d="M128 4h76a14 14 0 0 1 14 14v26a14 14 0 0 1-14 14h-40l-18 16 3-16h-21a14 14 0 0 1-14-14V18a14 14 0 0 1 14-14Z"/>
            </g>
            <path class="pelle" d="M112 34c40 0 72 30 72 74 0 50-32 92-72 92s-74-40-74-90c0-44 32-76 74-76Z"/>
            <path class="capelli" d="M42 102c-6-44 26-74 70-74 38 0 70 24 72 62-14-16-34-22-52-18 4-8 2-16-6-22-12 14-40 22-84 52Z"/>
            <path class="guance" d="M64 146a12 8 0 1 0 24 0a12 8 0 1 0-24 0M138 146a12 8 0 1 0 24 0a12 8 0 1 0-24 0"/>
            <g class="occhi">
              <circle cx="86" cy="118" r="13"/><circle cx="140" cy="118" r="13"/>
              <circle class="pupilla" cx="90" cy="121" r="6"/><circle class="pupilla" cx="144" cy="121" r="6"/>
            </g>
            <path class="tratto" d="M68 96c8-8 20-10 30-6M128 88c10-2 22 2 28 10"/>
            <path class="tratto" d="M96 168c10 6 26 6 34-2"/>
            <path class="nuvoletta" d="M128 4h76a14 14 0 0 1 14 14v26a14 14 0 0 1-14 14h-40l-18 16 3-16h-21a14 14 0 0 1-14-14V18a14 14 0 0 1 14-14Z"/>
            <text class="nuvoletta__testo" x="166" y="37" text-anchor="middle">Wait, what?</text>
          </svg>
        </div>
      </div>
      <div class="adesivo adesivo--cuore" aria-hidden="true" data-adesivo="cuore">
        <div class="adesivo__corpo" data-corpo>
          <svg viewBox="0 0 200 184" focusable="false">
            <path class="bordo" d="M100 176C52 140 8 108 8 62 8 30 32 8 60 8c18 0 32 8 40 22 8-14 22-22 40-22 28 0 52 22 52 54 0 46-44 78-92 114Z"/>
            <path class="cuore" d="M100 162C58 130 20 102 20 62c0-24 18-42 40-42 18 0 32 10 40 28 8-18 22-28 40-28 22 0 40 18 40 42 0 40-38 68-80 100Z"/>
            <path class="luce" d="M46 56c2-12 10-20 22-22"/>
          </svg>
        </div>
      </div>
    </figure>
  </main>

  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/gsap.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/SplitText.min.js"></script>
  <script src="https://cdn.jsdelivr.net/npm/gsap@3.15.0/dist/CustomEase.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.

30 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 sticker that follows the words” from Animorio and fit it into my site

What it does

A quote from a made-up comedy podcast, Late Bloomers, on a dark purple page. At the start the lines rise one at a time, each out of its own slot, and then the byline appears. Two words are apricot. Bring the mouse onto «awkward» and, right where you are, a sticker with a startled face and a speech bubble pops up, «Wait, what?»: it grows from its bottom edge with a soft bounce and then stays just above the pointer, a little late, as if tied to it by a short elastic. Move it sideways and it leans into the movement, up to ninety degrees if you go fast, and straightens up as soon as you stop; the eyes look that way too. On «love» a heart comes out: it opens like a round window from its middle and beats as long as it is out. Leave the word and the sticker goes away the way it came.

How it works

Two boxes for every sticker

The sticker sits in the same box as the quote, absolutely placed. The outer box goes where the script says: three CSS variables, --x and --y for the point and --giro for the lean, and a transform that moves it left by half its width and up by its whole height, so the pointer sits under its bottom edge. The inner box comes and goes with GSAP. Kept on two different elements, the movement that follows the pointer and the pop never wipe each other out.

The lag, frame by frame

The pointer events only write the point to reach. On every frame the sticker covers the FOLLOW share of the distance it has left: 0.22 means a little over a fifth, and that is the lag you see. The share is worked out from the real time since the last frame, so a 120 Hz screen moves it as a 60 Hz one does.

The lean is the speed

On every move the speed is how many pixels the pointer went sideways since the previous event, times LEAN, held within MAX_LEAN degrees. The lean catches up with it by the TURN share, and meanwhile the speed loses on every frame what SETTLE does not keep: if you stop it goes back to zero by itself, and the sticker straightens up without being told.

Popping up and opening

The face starts at scale zero and opacity zero, with its origin on the bottom edge, and reaches one in POP seconds on the BOUNCE curve, the site’s cubic-bezier(0.4, 1.65, 0.3, 1), which goes past and comes back. The heart is cut out instead: clip-path goes from inset(50% round 50%), a circle of radius zero in the middle, to inset(-100% round 50%), bigger than the whole drawing, on the same curve; meanwhile it beats, with the numbers in BEAT: in 0.7 seconds it grows a tenth, turns five degrees and comes back.

The lines and the words

SplitText splits the quote into lines and closes each line in a slot; the lines rise from 120% of their height in REVEAL seconds, one every STAGGER. While splitting, SplitText builds the words again: that is why the events are listened for on the quote rather than on the words, and the empty copies left at the cut are removed, otherwise they would be a second, invisible Tab stop.

Values to change

NameFileDefaultWhat it changes
FOLLOWscript.js0.22How much ground the sticker makes up every sixtieth of a second (0.22 on the site).
LEANscript.js1How many degrees of lean for each pixel moved sideways in one event (1 on the site).
MAX_LEANscript.js90The most it can lean, in degrees (90 on the site).
TURNscript.js0.18How quickly the lean catches up with the speed (0.18 on the site).
SETTLEscript.js0.82How much speed is left after each frame: lower straightens up sooner (0.82 on the site).
POPscript.js0.45How long a sticker takes to pop out (0.45 seconds on the site).
BOUNCEscript.jsCustomEase.create('bounce', '0.4, 1.65, 0.3, 1')The curve of the pop, which overshoots and comes back (the site’s).
BEATscript.js[0.7, 1.1, 5]The heart’s beat: seconds per beat, how much bigger it gets and how many degrees it turns (the site’s).
STAYscript.js1.2On a phone, how many seconds the sticker stays after the finger lifts.
REVEALscript.js1.2How long each line takes to rise (1.2 seconds on the site).
STAGGERscript.js0.08The delay between one line and the next.

Try them in the Playground

When to use it

For a quote, a headline or an introduction where one or two words deserve a surprise: whoever passes the mouse over them finds a drawing, a portrait or an object of the brand, and the text stays clean for everyone else.

On phones

The same, with a finger: the sticker comes out where you touch, follows you while you drag across the word and stays for a second after you lift your finger.

With reduced motion

The lines fade in; the stickers neither follow the pointer nor lean: they fade in and out, still above their word, and the heart does not beat.

Pitfalls

  • If the script writes the sticker’s transform to follow the pointer and the pop animates the same transform, one wipes out the other on every frame. The point and the lean go on one element, the pop on its child.
  • SplitText builds nested elements again: listeners set on the words beforehand are lost, and a word cut at its edge leaves an empty copy with the same tabindex. Listen on the container and remove the empty copies.
  • When splitting into lines SplitText hides the lines from screen readers and puts the whole text in an aria-label: the words to reach with the Tab key would end up inside a hidden element. With aria: 'none' the text is left as it is.
  • With pointerover and pointerout, the pointer passing between two letters of the same word makes the sticker leave and come back: check that relatedTarget is not inside the same word.
  • pointerleave does not bubble up to the parents, but a listener in the capture phase still hears it: some pointers, like scripted ones, send only that.

Libraries

GSAP 3.15.0, SplitText (GSAP) 3.15.0, Inter Tight (font variabile) 5.3.0.

Compressed with gzip, the three demo files weigh 6.5 KB; the libraries: GSAP + SplitText + CustomEase 35 KB.

Testing

Tested on 8 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 animations