ASCII <mv-ascii>

画像、動画、canvas、テキストをライブの ASCII アートとして描き直します。文字のランプとセルサイズを設定でき、モノクロまたはフルカラーに対応します。ポインターの軌跡では通過した場所の文字が太くなり、その後ろで落ち着いていきます。オプションの表示演出では、すべての文字をスクランブルしてから左から右へ確定させます。

仕組み

画像、動画、canvas、テキストをライブの ASCII アートとして描き直します。文字のランプとセルサイズを設定でき、モノクロまたはフルカラーに対応します。ポインターの軌跡では通過した場所の文字が太くなり、その後ろで落ち着いていきます。オプションの表示演出では、すべての文字をスクランブルしてから左から右へ確定させます。fillText は 1 行につき 1 回で、アイドル時にはループが停止します。

カテゴリーエフェクト
タイプWeb Component(<mv-ascii>)
ステータス安定版
Keywordsascii, 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

インストール

node scripts/add.mjs ascii --out ./src/marvelous

Marvelous UI の MCP サーバーを使う AI エージェント:install_components({ slugs: ["ascii"], target_dir: "<absolute path>/src/marvelous", framework: "react" })。

コピーされるファイル(依存関係を含む):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。

使い方

基本となるマークアップです。ここから属性、data-*、CSS 変数でカスタマイズしてください:

<div id="mv-ascii-demo">
  <style>
    #mv-ascii-demo { display: grid; grid-template-columns: repeat(auto-fit, minmax(min(16rem,100%), 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 canvas = document.getElementById("mv-ascii-demo-scene");
  const g = canvas.getContext("2d");
  // One still frame under reduced motion; the loop ends once the demo leaves the page.
  const still = matchMedia("(prefers-reduced-motion: reduce)").matches;
  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);
    if (!still && canvas.isConnected) requestAnimationFrame(frame);
  };
  requestAnimationFrame(frame);
</script>

API

Attributes

NameタイプDefaultDescription
textstringText to convert when there is no child media (otherwise the text content).
charsstring" .:-=+*#%@"Ramp from lightest to densest (the first character is used for empty space).
cellnumber10Size of one character (px): smaller = more detail.
modemono | colormonoSolid ink (--mv-ascii-color) or the source colors.
colorCSS colorvar(--mv-ascii-color, var(--mv-fg))Ink in mono mode (tokens accepted, follows the theme).
invertbooleanInverts density (in mono it already adapts to a dark or light ink).
contrastnumber1.15Contrast applied to luminance.
fitcover | containcoverSource framing.
hoverwake | nonewakePointer 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.
revealbooleanOn entering the viewport, every character scrambles, then locks in place from left to right.
labelstringAccessible name (otherwise the image alt, or the text).

Content structure

NameDescription
img | video | canvasSource (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

NameDefaultDescription
--mv-ascii-colorvar(--mv-fg)Ink in mono mode (re-read when the theme changes).
--mv-ascii-fontvar(--mv-font-mono)Character font (monospace).
--mv-ascii-ratiosource 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.

このページは AI で翻訳されています。翻訳の問題を報告