ASCII <mv-ascii>

이미지, 비디오, 캔버스 또는 텍스트를 실시간 ASCII 아트로 다시 그립니다. 문자 램프와 셀 크기를 설정할 수 있고, 단색 또는 풀 컬러를 지원하며, 포인터가 지나간 자리의 문자를 굵게 만들고 지나간 뒤에는 식어 가는 자취를 남기고, 선택적으로 모든 문자를 뒤섞은 뒤 왼쪽에서 오른쪽으로 고정하는 등장 효과를 제공합니다.

작동 방식

이미지, 비디오, 캔버스 또는 텍스트를 실시간 ASCII 아트로 다시 그립니다. 문자 램프와 셀 크기를 설정할 수 있고, 단색 또는 풀 컬러를 지원하며, 포인터가 지나간 자리의 문자를 굵게 만들고 지나간 뒤에는 식어 가는 자취를 남기고, 선택적으로 모든 문자를 뒤섞은 뒤 왼쪽에서 오른쪽으로 고정하는 등장 효과를 제공합니다. 행마다 fillText 한 번이며, 유휴 상태에서는 루프가 멈춥니다.

카테고리효과
유형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로 번역되었습니다. 번역 문제 신고