beta
ASCII <mv-ascii>
Redesenha uma imagem, um vídeo, um canvas ou um texto como arte ASCII ao vivo: rampa de caracteres e tamanho de célula configuráveis, monocromático ou colorido, uma esteira do ponteiro que engrossa os caracteres por onde passa e esfria atrás dele, e uma revelação opcional que embaralha cada caractere antes de fixá-los da esquerda para a direita. Um fillText por linha; o loop para quando está ocioso.
| Categoria | Efeitos |
|---|---|
| Tipo | Web Component (<mv-ascii>) |
| Status | beta |
| Keywords | ascii, canvas, image, video, text, retro, terminal, wake, effect |
When to use
- A retro, terminal or hacker aesthetic should render a photo, logo or video as live ASCII art
- A hero visual should unscramble into characters and react to the cursor with a trail
- Short display text should become a character-based artwork
Avoid when
- A heading should decode into readable text character by character → use Scramble Text instead
- The text must be indexable or selectable; it is drawn on a canvas and only exposed as an accessible name
Instalação
node scripts/add.mjs ascii --out ./src/marvelousAgente de IA com o servidor MCP do Marvelous UI: install_components({ slugs: ["ascii"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Arquivos copiados (dependências incluídas): tokens/tokens.css, core/base.css, core/canvas.js, core/dom.js, core/element.js, core/motion.js, core/observe.js, components/ascii/ascii.js, components/ascii/ascii.css.
Uso
Marcação de referência, para usar como ponto de partida e personalizar com atributos, data-* e variáveis CSS:
<div id="mv-ascii-demo">
<style>
#mv-ascii-demo { display: grid; grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr)); gap: 1.25rem; width: 100%; align-items: center; }
#mv-ascii-demo figure { margin: 0; display: grid; gap: .5rem; }
#mv-ascii-demo figcaption { font-size: .8rem; color: var(--mv-fg-subtle); text-align: center; }
#mv-ascii-demo .screen { border-radius: var(--mv-radius-xl); background: oklch(0.13 0.02 265); }
</style>
<figure>
<mv-ascii text="HELLO" reveal cell="9" style="--mv-ascii-color: var(--mv-accent-fg)"></mv-ascii>
<figcaption>Move the pointer across the letters to leave a wake.</figcaption>
</figure>
<figure>
<mv-ascii class="screen" mode="color" cell="8" label="Animated sunset over the sea, drawn with characters">
<canvas id="mv-ascii-demo-scene" width="320" height="200"></canvas>
</mv-ascii>
<figcaption>A live canvas redrawn in color, frame by frame.</figcaption>
</figure>
</div>
<script type="module">
// Demo source: a sun setting over rolling waves, drawn on a plain canvas.
const g = document.getElementById("mv-ascii-demo-scene").getContext("2d");
const frame = (ms) => {
const t = ms / 1000;
const sky = g.createLinearGradient(0, 0, 0, 120);
sky.addColorStop(0, "#1d1446");
sky.addColorStop(0.6, "#b4457a");
sky.addColorStop(1, "#ffb36b");
g.fillStyle = sky;
g.fillRect(0, 0, 320, 120);
const y = 92 + Math.sin(t * 0.4) * 10;
const glow = g.createRadialGradient(160, y, 8, 160, y, 90);
glow.addColorStop(0, "rgba(255,236,190,0.95)");
glow.addColorStop(1, "rgba(255,236,190,0)");
g.fillStyle = glow;
g.fillRect(0, 0, 320, 120);
g.fillStyle = "#ffe9b8";
g.beginPath(); g.arc(160, y, 30, 0, Math.PI * 2); g.fill();
for (let k = 0; k < 7; k++) {
const top = 112 + k * 13;
g.fillStyle = `hsl(${250 - k * 6} 55% ${34 - k * 4}%)`;
g.beginPath();
g.moveTo(0, 200);
for (let x = 0; x <= 320; x += 8) g.lineTo(x, top + Math.sin(x / (22 + k * 3) + t * (1.2 + k * 0.25)) * (3 + k));
g.lineTo(320, 200);
g.fill();
}
g.fillStyle = "rgba(255,222,170,0.55)";
for (let k = 0; k < 6; k++) g.fillRect(160 - 36 + k * 3 + Math.sin(t * 2 + k) * 4, 124 + k * 11, 72 - k * 6, 2);
requestAnimationFrame(frame);
};
requestAnimationFrame(frame);
</script>API
Attributes
| Name | Tipo | Default | Description |
|---|---|---|---|
text | string | Text to convert when there is no child media (otherwise the text content). | |
chars | string | " .:-=+*#%@" | Ramp from lightest to densest (the first character is used for empty space). |
cell | number | 10 | Size of one character (px): smaller = more detail. |
mode | mono | color | mono | Solid ink (--mv-ascii-color) or the source colors. |
color | CSS color | var(--mv-ascii-color, var(--mv-fg)) | Ink in mono mode (tokens accepted, follows the theme). |
invert | boolean | Inverts density (in mono it already adapts to a dark or light ink). | |
contrast | number | 1.15 | Contrast applied to luminance. |
fit | cover | contain | cover | Source framing. |
hover | wake | none | wake | Pointer wake: characters thicken where the pointer passes (a tap on touch) and thin out behind it. Any value other than none, such as the former ripple, turns it on. |
reveal | boolean | On entering the viewport, every character scrambles, then locks in place from left to right. | |
label | string | Accessible name (otherwise the image alt, or the text). |
Content structure
| Name | Description |
|---|---|
img | video | canvas | Source (direct child), kept in the DOM but hidden. Video and canvas are sampled every frame; a cross-origin image without CORS is shown as is. |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-ascii-color | var(--mv-fg) | Ink in mono mode (re-read when the theme changes). |
--mv-ascii-font | var(--mv-font-mono) | Character font (monospace). |
--mv-ascii-ratio | source ratio (16/9, 4/1 for text) | Element proportions, set automatically. |
Accessibility
role=img with an accessible name (label, image alt or text); the canvas and the source are aria-hidden. Reduced motion (OS setting or data-motion="reduce"): no wake and no reveal, a still picture (a video is still sampled while it plays). The frame loop stops off screen and when nothing moves. Forced colors: the artwork keeps its colors inside a system outline.