Skip to content
animorio
IT
All components

The e-ink button

Buttons, TransitionsCanvasAdvancedCompressed weight: Demo 6.9 KBGSAP 28 KBFreeDownload the zip

A book reader's Sync library button is a piece of electronic paper in its bezel: pressed, it does a real e-reader refresh, a black flash, a negative, white and the new picture laid down in bands of rows. While it syncs the circular arrows turn in jerks, a few frames a second in pure black and white, each leaving a ghost of the one before; when it is done only the words refresh, with their own little flash.

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 e-ink button</title>
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@fontsource-variable/literata@5.3.0/opsz.css">
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <main class="stage">
    <!-- the e-ink button: a book reader's Sync library button drawn as a piece of electronic paper in its bezel. The
         screen is a low resolution canvas with real e-ink refreshes: flashes, a spinner in black and white at a few
         frames a second leaving ghosts behind it, and a partial refresh of the words when it is done -->
    <div class="eink" data-eink>
      <button class="eink__btn" type="button" aria-label="Sync library. Last synced 2 days ago" data-btn data-autoclic>
        <span class="eink__bezel" aria-hidden="true"></span>
        <span class="eink__screen" aria-hidden="true">
          <canvas class="eink__canvas" width="312" height="92" data-canvas></canvas>
          <span class="eink__glass"></span>
        </span>
      </button>
      <p class="sr" aria-live="polite" data-live></p>
    </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.

20 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 e-ink button” from Animorio and fit it into my site

What it does

On a white background a graphite bezel holding a piece of electronic paper, warm grey and matte: on the left an icon of two arrows in a circle, a dithered grey rule, and on the right Sync library and, smaller and grey, Last synced 2 days ago, in a book typeface. Close up you can see the pixels and the dithering of the greys. When the page opens the screen wakes like a reader: a black flash, the picture in negative, white, then the picture. With the mouse over it the bezel rises a little (ink does not glow); pressing, it goes down. Clicking, the whole screen goes black, then shows the new screen in negative, goes white and the new screen is laid down from top to bottom in bands of rows, the band being written still grey: Syncing library, Checking 214 books. The arrows turn in jerks, five frames a second, drawn only in black and white with dithering, and each frame leaves a grey ghost of the one before. Halfway the line underneath becomes Downloading new books. When it is done the arrows stop where they started, only the area of the words flashes black and white and Synced · 3 new books, Library up to date appears, with a tick in the arrows, while the ghosts of the turning stay round them. A few seconds later the words go back to Sync library, Last synced just now with the same small refresh, and a very faint ghost of the tick stays in the arrows.

How it works

A low resolution screen

The screen is a canvas of 312 by 92 pixels, one per CSS pixel, scaled up with image-rendering: pixelated: on double density screens each pixel of ink is a little square of four, and the pixels show up close as on a real reader. Each screen is drawn in a hidden canvas: the arrows are SVG paths passed to Path2D and turned, the words are real type in Literata, rasterised with fillText.

The dithering

The grey of every pixel goes through an ordered dither with the 4 by 4 Bayer matrix: for normal updates it comes down to 16 levels (the matrix chooses between the two nearest), for fast ones to pure black and white, like a reader's fast mode. So the rule, the grey line underneath and the edges of the letters show the regular pattern of the dither.

An ink buffer and the ghosting

What shows is not the picture but a buffer of numbers from 0 (black) to 1 (white): the state of the ink. A full refresh takes it exactly to the new picture; an update without a flash moves it towards the new picture but leaves a share of the old one (GHOST): that is the ghosting, and it builds up frame after frame. The colour of each pixel is a mix of the paper and ink colours from the CSS, with a little fixed grain.

Jerks counted on the ticker

Every change of the screen is a step in a queue: GSAP's ticker (requestAnimationFrame) adds up the time, with the step capped at 1/30 of a second, and when the step's time has passed it changes the ink once. The full refresh is black, negative, white and seven bands of rows; the spinner is one frame every 1/SPIN_FPS of a second, turned by SPIN_STEP degrees, updated only in the icon's rectangle. The canvas is drawn again only when the ink has changed.

The partial refresh

At the end of the sync only the rectangle of the words flashes black and white and shows the new words clean; the tick in the arrows comes in with the same step, over the ghosts of the turning, which stay. The number of spinner frames is a whole number of half turns, so the arrows stop exactly where they started.

Values to change

NameFileDefaultWhat it changes
SYNCscript.js2.6The seconds the pretend sync lasts.
SPIN_FPSscript.js5The frames a second of the spinner: e-ink is slow.
SPIN_STEPscript.js30How many degrees the arrows turn at each frame.
GHOSTscript.js0.26How much of the old picture a fast black and white update leaves behind.
FLASHscript.js0.11The seconds of each step of the full refresh (black, negative, white).
HOLDscript.js4The seconds Synced stays before the button is back at rest.
NEW_BOOKSscript.js3How many new books the sync finds.
TEXTSscript.js{ rest: ['Sync library', 'Last synced 2 days ago'], … }The words on the screen at each moment: the title and the line underneath.

Try them in the Playground

When to use it

For a reading app, a notes archive, a publishing site or any product that wants the calm character of paper: syncing, downloading, refreshing. Electronic ink is slow on purpose: it belongs on an action that takes a few seconds, not on a button pressed all the time.

On phones

The bezel is 340 pixels wide and fits a 390 pixel screen; under 380 pixels it shrinks a touch. On double density screens the pixels of the ink are little squares of four, as on a reader. A touch takes the bezel down like a click.

With reduced motion

No flashes and no spinning arrows: the words change in place with an update without a flash, Syncing library, Downloading new books, Synced · 3 new books with the tick, and then back to rest; the bezel does not rise under the mouse.

Pitfalls

  • Going back to rest with an update without a flash over the whole screen left the ghost of Synced · 3 new books under Sync library: two lines of words on top of each other. The words always get their clean partial refresh; the ghosting stays only in the icon.
  • Synced · 3 new books at 21 pixels ran off the right edge of the screen: the title is 19.5 pixels and starts a little further left.
  • Rasterising the text before the typeface had loaded left the screen in the fallback face: the screen is drawn at once with the fallback, then, when Literata is ready, the waking flash draws it again.
  • If the number of spinner frames is not a whole number of half turns the arrows stop crooked: the number is rounded to whole half turns (the arrows are symmetrical).
  • The steps' times are counted by adding up the ticker's time, not frames: with the library's bench slowing the clock, or on a 120 Hz screen, the spinner stays at SPIN_FPS frames a second.

Libraries

GSAP 3.15.0, Literata (font variabile) 5.3.0.

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

Testing

Tested on 5 October 2026

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

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