Zum Inhalt springen
Michael Blaess
IT-Consulting
Michael Blaess
DE

retro-text-effects.js: terminal text effects for the browser

JavaScriptAnimationRetroOpen Source

If you live in the terminal you probably know TerminalTextEffects (TTE) - the Python library that decrypts, rains or explodes text in your terminal. I loved it and wanted those effects in my own web projects too - without forced canvas, without a framework, without a build step. So I ported TTE: retro-text-effects.js is dependency-free vanilla JavaScript, shipped as a single file with no external requests - which also keeps GDPR lawyers relaxed.

Live demo

Enough talk - the library runs right here in this article (the buttons trigger each effect):

 R E T R O - T E X T - E F F E C T S . J S

43 effects for the browser one file - zero dependencies the text stays selectable no canvas needed (unless you want it)

Text
Canvas
Style

Any emoji becomes an ASCII figure

This one effect does not come from TerminalTextEffects. The idea comes from the website of the terminal emulator Ghostty: a ghost made of text characters floats there, surrounded by a flickering ring. I wanted to rebuild that, but not as a finished image - for any character you like.

The effect is called aura and it works unlike all the others: it does not generate text, it renders any icon as animated ASCII art. The emoji is drawn on an invisible canvas, the result is cut into a cell grid, and every cell is mapped onto a character by coverage and brightness.

Glyph
Aura
Motion

Three things turned out to be hard.

The outline comes from the edge, not from the brightness. If you only map brightness onto characters, the figure frays at the border, because the emoji fades out softly there. Only when every cell with an empty neighbour got a hard @ did the figure get a clean outline.

The ring has to know that text cells stand upright. The distance to the figure is computed with a distance field. If a step to the left counts as much as a step upward, the ring turns into a flat ellipse, because a text cell is about twice as tall as it is wide. So the horizontal step only counts a bit more than half.

Spinning must not be a CSS transform. The obvious route would be to tilt the finished figure with transform: rotate(). But then the letters tilt along, and the grid that makes the figure a figure in the first place falls apart. Instead the emoji is rasterised once in sixteen poses, and the effect replays those poses as a loop. The hop is an offset by whole rows.

If you would rather have the drawing once and without animation, use asciiArt('๐ŸŽƒ'). The function returns it as a string, and that string can go into any other effect, decrypt for instance.

One limitation comes with it: emoji fonts differ per operating system. The same ghost looks different on Windows, macOS and Linux, because every system ships its own emoji font. If you need a fixed result, generate the drawing once and put it into your markup as text.

Four effect groups

By now there are 43 effects, split into four groups:

  • Text effects (15 of them) run on a plain <pre> block and only rewrite its text content - no canvas, the text stays selectable, box-drawing characters stay aligned, and the color is inherited from your element. This group includes decrypt, print, matrix, burn, vhstape, waves, slide, pour and more.
  • Canvas effects (24 of them) lay a temporary canvas over the element for free 2D character motion (fireworks, blackhole, rain, swarm, synthgrid, thunderstorm and others), then fade it out and reveal the untouched text below. Some carry a 2 in the name (matrix2, decrypt2, print2, overflow2): those are the canvas twins of the text effects with the same name - more elaborate, with more motion.
  • Style effects (3 of them) change the element in place without rewriting the text: crt lays a persistent phosphor look with scanlines over it, colorshift a sliding gradient, highlight a single band of light.
  • Art effects (one so far) do not read text from the page. aura renders any icon as the animated ASCII art above and keeps running until you stop it. It is the only effect in the library without a counterpart in TerminalTextEffects, inspired by Ghostty.

Integration in three lines

<pre id="log">=== System ready ===</pre>

<script src="retro-text-effects.min.js"></script>
<script>
  RetroTextEffects.decrypt('#log');
</script>

That is the whole integration: one script tag exposes window.RetroTextEffects, then you call an effect with an element or a CSS selector. Every effect returns a small controller:

const fx = RetroTextEffects.print('#log', { cps: 80 });
fx.cancel();        // stop early
await fx.finished;  // promise, resolves when the animation ends

Options like speed, fps, glyphs or color can be tuned per effect. Font, color and character grid are read from the target element, so there is no visible jump when the real text takes over.

Why single file?

The library is built for the case where there is no npm project: a CMS page, a widget, a static demo. Copy the file, add a script tag, done - no dependencies, no build, no external requests. That is exactly how the demo in this article is embedded, by the way.

Links: GitHub repo ยท Live demo of all effects

“It is not that we have too little time, but that we waste much of it.”

- Seneca