This page is for using ascii.rest with no framework, in plain TypeScript or JavaScript. You need only a bundler that can import from npm, such as Vite. mount() plays a piece in a <pre> or on a <canvas> on your page. Everything in the package is typed. The examples are in TypeScript. In JavaScript, leave out the types and the !.
Two words used on this page. A piece is one animation, like donut. A frame is the picture at one moment, as a string of text.
Put a <pre> in your page:
<pre id="art"></pre>
Then play a piece in it:
import { mount } from "ascii.rest";
import { donut } from "ascii.rest/pieces";
mount(document.querySelector<HTMLElement>("#art")!, donut);
Set up a new project
These steps make a new TypeScript project with Vite and play donut in it. If you already have a project with a bundler, you only need step 2 and the code above.
-
Make the project and go into its folder. If it asks “Install with npm and start now?”, answer No: you start it in step 5.
npm create vite@latest my-app -- --template vanilla-ts cd my-app -
Install ascii.rest. Because you skipped the install in step 1, this also installs Vite and the rest of the project.
npm install ascii.rest -
In
index.html, replace<div id="app"></div>with<pre id="art"></pre>. -
Replace everything in
src/main.tswith the TypeScript above, the block that starts withimport { mount }. -
Start the dev server, then open the URL it prints. By default that is
http://localhost:5173.npm run dev
The donut turns. To play another piece, import it from ascii.rest/pieces by its export, in camelCase: nightCoast, bigText, rust.
Choose a pre or a canvas
mount() draws into whichever element you give it. A <pre> shows text. A <canvas> shows colour.
A piece has colours of its own when piece.meta.palette is set. Every logo, company, distro and scene does: these 88 are the coloured pieces. The other 129 are text pieces: plain text in your page’s colour.
| element | what you get |
|---|---|
<pre> |
Text in the pre’s own colour and font. Every piece works here. A coloured piece shows in that one colour. |
<canvas> |
A coloured piece in its own colours, over its own background if it has one. A text piece is drawn in the canvas’s CSS color. |
Here is the typescript logo in a <pre>, then on a <canvas>:
To pick the right element for any piece:
import { mount } from "ascii.rest";
import { nightCoast } from "ascii.rest/pieces";
const el = document.createElement(nightCoast.meta.palette ? "canvas" : "pre");
el.setAttribute("role", "img"); // so screen readers announce it as a picture
el.setAttribute("aria-label", nightCoast.meta.name);
document.body.append(el);
mount(el, nightCoast);
How a canvas is sized
- It fills the width of its container. Give it a CSS
widthto make it smaller. - Its height follows from the piece’s shape.
mount()sets its CSSaspect-ratiofor you. - When it changes width, it redraws at the new size, sharp on high density screens.
- Where the piece has no background colour, the canvas is transparent and your page shows through.
Style the pre
A <pre> already uses a monospace font. These styles keep rows evenly spaced and stop the font from joining characters. The <ascii-art> tag gives its own <pre> the same styles.
<style>
pre {
margin: 0;
font: inherit;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", "ascii.rest mono", monospace;
line-height: 1.2;
letter-spacing: 0;
font-variant-ligatures: none;
}
/* Android's monospace font has no box drawing or block characters. This fills them in from a 3 KB font. */
@font-face {
font-family: "ascii.rest mono";
src: url(https://ascii.rest/fonts/ascii-rest-mono.woff2) format("woff2");
unicode-range: U+00B0, U+00B7, U+2022, U+2500-259F, U+25CF;
font-display: swap;
}
</style>
The browser downloads that font only when the fonts before it lack a character.
Change a piece’s options
Pass options as the third argument to mount().
import { mount } from "ascii.rest";
import { archLinux, bigText, donut } from "ascii.rest/pieces";
mount(document.querySelector<HTMLElement>("#title")!, bigText, { text: "hello" });
mount(document.querySelector<HTMLCanvasElement>("#logo")!, archLinux, { scan: 0 }); // no scan line: the logo holds still
mount(document.querySelector<HTMLElement>("#art")!, donut, { fps: 12 });
| option | what it does | default |
|---|---|---|
| any option of the piece | Changes the piece. bigText takes text. archLinux takes scan: the seconds between the lines that sweep down the logo. |
the piece’s meta.options |
fps |
Frames a second. Fewer frames means less work: the piece moves at the same speed, less smoothly. 0 draws one frame and stops. |
the piece’s meta.fps, or 30 |
motion |
true plays the piece even when the reader has asked for reduced motion. Use it only on a page with its own play button. |
false |
To see which options a piece takes, and their defaults, read meta.options:
import { archLinux, bigText } from "ascii.rest/pieces";
console.log(bigText.meta.options); // { text: "hello" }
console.log(archLinux.meta.options); // { scan: 5 }
Have TypeScript check the options
mount() accepts any options object, so it doesn’t check the values you pass. Every piece that takes options exports a type for them, named after the piece: BigTextOptions, SpinnersOptions. Import it from the piece’s own module and type your options with it:
import { mount } from "ascii.rest";
import { bigText } from "ascii.rest/pieces";
import type { BigTextOptions } from "ascii.rest/pieces/big-text";
const options: Partial<BigTextOptions> = { text: "hello" };
mount(document.querySelector<HTMLElement>("#title")!, bigText, options);
Now { text: 42 } is an error, because text takes a string. These types allow extra keys, so a misspelt option name, like { txt: "hello" }, still compiles.
Stop it
mount() returns a function. Call it to stop the piece.
import { mount } from "ascii.rest";
import { donut } from "ascii.rest/pieces";
const pre = document.querySelector<HTMLElement>("#art")!;
const stop = mount(pre, donut);
document.querySelector("#stop")!.addEventListener("click", () => stop());
Stopping does three things:
- It stops drawing frames.
- It removes the observers and listeners
mount()added. - It leaves the last frame in the element. Set
pre.textContent = ""to clear it.
Call stop() before you mount another piece in the same element. If you don’t, both keep drawing into it. This swaps one piece for another:
import { mount, type Piece } from "ascii.rest";
import { donut } from "ascii.rest/pieces";
const pre = document.querySelector<HTMLElement>("#art")!;
let stop = mount(pre, donut);
function show(piece: Piece) {
stop();
stop = mount(pre, piece);
}
Mounting a piece again starts it from the beginning.
When it plays and when it pauses
mount() saves work for you. You don’t have to do anything for this.
- It plays only while the element is on screen. When you scroll it out of view, it pauses.
- It pauses while the browser tab is hidden.
- While it is paused, its time stops too. When it plays again, it carries on from the same moment.
- If the reader has asked for reduced motion in their system settings, it shows one still frame and doesn’t play. That is the moment the piece names in
meta.still, or its first frame. A banner that types itself in shows every letter. - If the reader changes that setting while the page is open, it starts or stops to match.
Play a banner
banner() from ascii.rest/banner turns any text into a piece, so mount() plays it like any other.
import { mount } from "ascii.rest";
import { banner } from "ascii.rest/banner";
mount(document.querySelector<HTMLElement>("#title")!, banner("hello", { shadow: "rounded" }));
A banner with a color is a coloured piece, so mount it on a <canvas> to see the colours. The canvas’s CSS color tells the banner whether your page is light or dark: dark text means a light page.
import { mount } from "ascii.rest";
import { banner } from "ascii.rest/banner";
const canvas = document.querySelector<HTMLCanvasElement>("#title")!;
canvas.style.width = "24rem";
mount(canvas, banner("hello", { color: ["#f97316", "#f778ba"], shadow: "rounded" }));
banner() throws if an option is wrong, such as a font name it doesn’t know. The error says what to change. Every banner option, from fonts to colours, is on banners.
Load a piece by its name
Use these when the piece is chosen while the page runs, for example from a menu or the URL. They all come from ascii.rest.
| export | what it is |
|---|---|
load |
One function for each piece, by name. await load["night-coast"]() imports that piece. |
names |
The names of every piece, in alphabetical order. |
isPiece(name) |
true if a string is a piece’s name. In TypeScript, it also narrows the string to the PieceName type. |
canvas |
A Set of the names of the coloured pieces: the ones to draw on a <canvas>. |
These use the piece’s name, with dashes: night-coast. The export from ascii.rest/pieces is the same piece in camelCase: nightCoast. So isPiece("night-coast") is true and isPiece("nightCoast") is false.
load imports a piece only when you call its function. Bundlers such as Vite put each piece in its own file, so your page downloads only the pieces it plays.
This page plays the piece named in its URL, like ?piece=night-coast:
import { canvas, isPiece, load, mount } from "ascii.rest";
const name = new URLSearchParams(location.search).get("piece") ?? "donut";
if (isPiece(name)) {
const piece = await load[name]();
const el = document.createElement(canvas.has(name) ? "canvas" : "pre");
document.body.append(el);
mount(el, piece);
}
This one makes a menu of every piece and plays the one you pick:
import { canvas, isPiece, load, mount, names } from "ascii.rest";
const select = document.createElement("select");
for (const name of names) select.add(new Option(name));
const box = document.createElement("div");
document.body.append(select, box);
let stop = () => {};
select.addEventListener("change", async () => {
const name = select.value;
if (!isPiece(name)) return;
const piece = await load[name]();
stop();
const el = document.createElement(canvas.has(name) ? "canvas" : "pre");
box.replaceChildren(el);
stop = mount(el, piece);
});
Draw a frame yourself
You don’t need mount() to get a frame. A piece is a module with meta and a default function:
- Call
default()with the piece’s options. It returns the piece’s frame function. - Call the frame function with a time in seconds. It returns the frame at that moment, as a string.
import { banner } from "ascii.rest/banner";
const frame = banner("hi", { shadow: "rounded" }).default();
console.log(frame(0));
That prints:
▓▓╮ ▓▓╮ ▓▓╮
▓▓│ ▓▓│ ▓▓│
▓▓▓▓▓▓▓▓│ ▓▓│
▓▓╭───▓▓│ ▓▓│
▓▓│ ▓▓│ ▓▓│
╰─╯ ╰─╯ ╰─╯
The same works for every piece in ascii.rest/pieces:
import { bigText, donut } from "ascii.rest/pieces";
const frame = donut.default();
const text = frame(1.5); // the donut at 1.5 seconds
const hello = bigText.default({ text: "hello" })(0); // options go to default()
console.log(text, hello);
What to know about frames:
- A frame is
meta.rowslines ofmeta.colscharacters, joined with\n. There is no newline at the end. - Drawing a frame needs no browser. It works in Node, in a web worker and on a server.
- Most pieces give the same frame for the same time, whatever you drew before. A few are simulations, like
falling-sandanddoom-fire: each call moves them on from the call before. Call those with a rising time, asmount()does. - A piece with
meta.clockset, likedigital-clock, shows the real time or date.
Tell a frame about the page
The frame function takes a second argument, an Env object:
| field | what it does | default |
|---|---|---|
paper |
Set it to true when you draw dark text on a light background. Pieces drawn in shades, like donut, swap their light and dark characters so they still read. |
false |
color |
For a coloured piece: a Uint8Array of cols * rows numbers. The frame writes each cell’s colour into it, as an index into meta.palette. |
none |
import { donut } from "ascii.rest/pieces";
const onWhite = donut.default()(1.5, { paper: true });
console.log(onWhite);
To get the colours of a coloured piece, pass a color array and read it back. Cell x, y is at index y * cols + x:
import { typescript } from "ascii.rest/pieces";
const { cols, rows, palette } = typescript.meta;
const color = new Uint8Array(cols * rows);
const lines = typescript.default()(0, { color }).split("\n");
const x = 10, y = 5;
console.log(lines[y][x], palette[color[y * cols + x]]); // 8 #007acc: a cell of the blue square
Run your own loop
This loop is a bare version of mount(), with no pausing, no colour and no canvas:
import { donut } from "ascii.rest/pieces";
const pre = document.querySelector<HTMLElement>("#art")!;
const frame = donut.default();
const start = performance.now();
function tick(now: number) {
pre.textContent = frame((now - start) / 1000);
requestAnimationFrame(tick);
}
requestAnimationFrame(tick);
Play a frame function of your own
mount() also takes a plain function instead of a piece. That function must return a frame function. With no meta, it plays at 30 frames a second, and a canvas gets 80 columns by 24 rows.
import { mount } from "ascii.rest";
const dots = () => (t: number) => ".".repeat(1 + (Math.floor(t * 4) % 10));
mount(document.querySelector<HTMLElement>("#art")!, dots);
To give your own picture a size, colours and options, write it as a piece: see your own pieces.
Use the types
These types come from ascii.rest:
| type | what it is |
|---|---|
Piece |
A piece module: { meta, default }. Piece<O> gives its options the type O. |
Meta |
What a piece says about itself, as piece.meta: its name, size, frame rate, options and colours. |
Frame |
A frame function: (t: number, env?: Env) => string. |
Env |
The frame function’s second argument: { paper?: boolean; color?: Uint8Array }. |
Options |
Any piece’s options: Record<string, unknown>. |
Category |
The 15 categories, like "scenes", "logos" and "distros". |
PieceName |
The name of every piece, as a union of strings. |
MountOptions |
The third argument of mount(): a piece’s options, plus fps and motion. |
Every field of Meta, what it does and its default, is in fill in its meta.
A helper that takes any piece and plays it in the right element:
import { mount, type MountOptions, type Piece } from "ascii.rest";
export function addPiece(parent: HTMLElement, piece: Piece, options?: MountOptions): () => void {
const el = document.createElement(piece.meta.palette ? "canvas" : "pre");
el.setAttribute("role", "img");
el.setAttribute("aria-label", piece.meta.name);
parent.append(el);
return mount(el, piece, options);
}
PieceName catches a misspelt name before the page runs:
import type { PieceName } from "ascii.rest";
const favourites: PieceName[] = ["donut", "night-coast", "rust"];
console.log(favourites);
The other modules export their own types:
import type { BannerOptions, BannerPiece, Colors, Effect, Font, FontName, ShadowName, Themed } from "ascii.rest/banner";
import type { BannerSvgColor, BannerSvgOptions, Loop, Part, Shot, SvgOptions } from "ascii.rest/svg";
import type { Bannered, Output, Played, PlayOptions, PrintOptions } from "ascii.rest/terminal";
Each is described on api.
Next
- your own pieces: write a piece and play it with
mount(). - banners: every option of
banner(). - api: everything each module exports.