Skip to content
animorio
IT
Back to the library

The quote marks that squeeze the line

Text, HoverGSAP, CSS onlyMediumCompressed weight: Demo 8.4 KBGSAP 28 KBFreeDownload the zip

A short quote in the minimal style: two giant quote marks come in from opposite sides and close on the line like pincers, squeezing it on the width axis until it gets denser; then they let go, the line breathes out, wide and light, and the author's name runs underneath, riding the tip of a hairline.

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 quote marks that squeeze the line</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/roboto-flex@5.3.0/standard.css">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/fraunces@5.3.0/opsz.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage" data-stage>
    <!-- what people read: the quote and its author; everything drawn below is only its picture -->
    <p class="sr" data-read></p>

    <!-- the script builds one line per quote in here, letter by letter -->
    <div class="phrases" data-phrases aria-hidden="true"></div>

    <!-- the two quote marks: they come in from the sides and close on the line like pincers -->
    <span class="mark mark--open" data-mark aria-hidden="true">&ldquo;<i></i></span>
    <span class="mark mark--close" data-mark aria-hidden="true">&rdquo;<i></i></span>

    <!-- the author: a hairline draws under the quote and the name rides its tip -->
    <div class="cite" data-cite aria-hidden="true">
      <i class="cite__line" data-line></i>
      <span class="cite__clip" data-clip><span class="cite__name" data-name></span></span>
      <i class="cite__tick" data-tick></i>
    </div>
  </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.

17 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 quote marks that squeeze the line” from Animorio and fit it into my site

What it does

On ivory paper a short quote rises, letter by letter: «Words need air.», a little wide and light. Then two giant red quote marks, an opening one and a closing one, come in from the two sides faster and faster, hit the ends of the line and keep pushing like pincers: the line squeezes on the width axis until it is very narrow and darker, the marks gain weight and tremble slightly under the strain, and at the blow the letters give a small jolt, from the ends towards the middle. After a moment the pincers open: the line breathes out, wider and lighter than its measure, then goes back to its place with a small bounce, while the marks turn thin and step back. Under the line a hairline draws itself and the author's name rides its tip, narrow while it runs and full when it stops; at the end of the hairline a small red tick pops up. The line breathes once more, then the name leaves past the end of the hairline, the hairline withdraws, and the marks close again: they crush the line until it disappears, touch in the middle and fly off to the sides. The next quote arrives, «Say less, slowly.», and it all starts again. Pointing at the line with the mouse (or keeping a finger on it on a phone) makes it wider and lighter, and the marks give it room.

How it works

Two variable typefaces, one axis each

The line is set in Roboto Flex with Fontsource's «standard» file, which keeps the optical size axis: with «opsz» 144 written in font-variation-settings the wdth axis goes from 25 (a sliver) to 151 (very wide), and the weight from 100 to 1000. The quote marks are in Fraunces, which has weight from 100 to 900: light when they arrive, full when they squeeze. Every letter of the line is an inline-block span, for the jolt and the entrance; width and weight are written once, on the whole line.

One number for the squeeze

Everything depends on q: 0 is the line at rest (wdth 100, weight WEIGHT), 1 the squeezed line (wdth SQUEEZE, weight plus 80), −1 the line wide and light (wdth 151, weight 220). A function turns q into the two axes, and the timeline tweens move only q: it comes in at −0.35, reaches 1 with power3.out after the blow, goes back to −0.45 × BREATH with expo.out and settles on 0 with a damped spring written as an ease. The breath while it is read is a sine.inOut with yoyo; the pointer takes up to 0.28 off q.

Marks attached to the edges

On every frame the axes are written, then the getBoundingClientRect of the line is read just once: the inner edge of the ink of each mark stands at a distance g from the line, plus m times the way off the screen. Where the ink of “ and ” starts and ends is measured on a canvas with measureText (actualBoundingBoxLeft and Right) at nine weights, interpolated in between. So the pincers always touch the line, whatever its width at that moment.

The blow, the pressure, the final squeeze

The marks come in with power3.in, faster and faster, and the squeeze starts again from that speed. At the contact a number hit squashes them (scaleX 0.84 and scaleY 1.07 from the inner edge) and comes back with elastic.out; the letters give a 0.05 em jolt staggered from the ends towards the middle. While they squeeze, pr makes them tremble by a pixel along a fast sine. On the way out q goes back to 1 and a second number k crushes the line with scaleX down to zero while g goes to zero: the marks touch in the middle and fly away with power4.in.

The name on the tip of the line

The hairline under the quote is as long as the line at rest and draws with scaleX from the left (expo.out). The name sits in a window with overflow hidden as wide as the hairline, and its right edge is always on the tip: it comes out of the left end and rides the hairline until it stops at the end, aligned right. While it runs its wdth is 40 and it widens to 100 as it slows down; on the way out it does the opposite and slides past the end of the hairline.

Values to change

NameFileDefaultWhat it changes
QUOTESscript.js[['Words need air.', 'Mara Lindqvist'], ['Say less, slowly.', 'Teo Ballard']]The quotes, one after the other: [the line, who said it]. Short lines, on one row: the longer it is, the smaller the type gets.
WEIGHTscript.js560The weight of the line at rest, from 100 (a hairline) to 1000 (black). Squeezed it weighs 80 more.
SQUEEZEscript.js25How tight the marks squeeze the line, on the wdth axis: 25 is the narrowest the typeface goes, 100 means they do not squeeze it.
MARKscript.js2.4The size of the quote marks, in sizes of the line.
BREATHscript.js0.7How far the line breathes out when the pincers open: 0 it just goes back, 1 it gets wide and light.
HOLDscript.js2.4How many seconds the quote stays, breathing, before the marks close on it again.
SPEEDscript.js1The speed of the whole animation: 2 is twice as fast, 0.5 half as fast.
LOOPscript.jstrueWhether the first quote comes back after the last one. With false the last one stays in place.

Try them in the Playground

When to use it

For a quote or a review to put in the spotlight: a client's line on a studio's page, a restaurant's motto, a critic's line for a book or a show, a testimonial on a landing page. It works with short lines, read at a glance, and with a few quotes in a row.

On phones

The same animation: line, marks and gaps are measured again to fit the width of the screen, even when the line breathes out. Keeping a finger on the line makes it breathe as with the mouse; lifting it, it goes back to its place.

With reduced motion

Nothing moves or squeezes: line, marks, hairline and name fade in already in place, stay, then fade out and the next quote appears. No breath under the pointer.

Pitfalls

  • The marks have to be attached to their ink, not to their box: the typeface leaves a different space on the sides of “ and ”, and that space changes with the weight. The canvas measures it (measureText with actualBoundingBoxLeft and Right) at nine weights, interpolated in between.
  • Weight widens the letters too: with 240 more weight the line squeezed to wdth 25 stayed almost as wide as at rest (492 px against 622 at a 100 px type size, and with WEIGHT 760 it did not squeeze at all). Squeezed it weighs only 80 more, and WEIGHT goes up to 700 at most.
  • The canvas only knows font-weight, not font-variation-settings: that is why the weight of the marks is written with style.fontWeight (which in a variable typeface moves the wght axis), so the page and the measurement use the same drawing.
  • The width of the line has to be read after writing the axes and before moving the marks, once per frame: reading and writing in turns makes the page recalculate several times.
  • With Fontsource's «wdth» file Roboto Flex barely squeezes: it has the optical size fixed at 14. The «standard» file with «opsz» 144 written by hand is needed.
  • The fromTo tweens of a timeline write their starting values as soon as they are created: once built, the state would be the one of the last exit (marks in the middle). At the end of the build prepare(0) sets the first quote back, empty, with the marks off screen.
  • A damped spring used as an ease, with a damping that does not look like a cartoon, passes its target by only a few hundredths: for the breath the line is first sent past it (expo.out to −0.45 × BREATH) and then the spring brings it back to rest.

Libraries

GSAP 3.15.0, Roboto Flex (font variabile, assi opsz, wght e wdth) 5.3.0, Fraunces (font variabile, assi opsz e wght) 5.3.0.

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

Testing

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