canvasplayer

Graffiti Markup Language playback. Source on GitHub

Embed options

Three ways to show Katsu #161. Each frame loads only the modules it needs. Change ?id=161 in the public snippets to select another numeric tag ID.

Animation

The drawing fills its frame and loops without controls. With reduced motion enabled, it shows the finished tag.

Open animation

<iframe src="https://jamiew.github.io/canvasplayer/embeds/animation.html?id=161"
  title="Animated graffiti drawing"
  loading="lazy"
  style="width:100%;aspect-ratio:1;border:0">
</iframe>

Full player

Play, pause, scrub and change the ink. Use Remove player to stop playback and release its resources.

Open player

<iframe class="player-frame" src="https://jamiew.github.io/canvasplayer/embeds/player.html?id=161"
  title="Graffiti player"
  loading="lazy"
  style="width:100%;height:800px;border:0">
</iframe>

This fixed-height embed scrolls when needed. The gallery resizes it to fit its content.

Static drawing

Draw once with parse, prepare and paint. No player or animation loop.

Open static drawing

<iframe class="drawing-frame" src="https://jamiew.github.io/canvasplayer/embeds/drawing.html?id=161"
  title="Static graffiti drawing"
  loading="lazy"
  style="width:100%;height:800px;border:0">
</iframe>

The gallery resizes this frame to fit the drawing and text.

Embed on another site

The snippets and Open links use public GitHub Pages URLs, ready to paste into another site once deployed. Local changes may not be published yet. These previews use relative URLs so local development and sizing stay same-origin.

All three embeds accept a numeric ?id, for example ?id=147. Omit it or use ?id=161 for bundled Katsu data without an external API request. Other IDs use 000000book JSONP because its API has no CORS header. JSONP executes scripts from that service; allow only trusted script sources in your CSP.

<script>
document.querySelectorAll('.player-frame, .drawing-frame').forEach(frame => {
  let observer;
  frame.addEventListener('load', () => {
    if (observer) observer.disconnect();
    const body = frame.contentDocument?.body;
    if (!body) return;
    observer = new ResizeObserver(() => {
      const style = getComputedStyle(frame);
      const border = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
      frame.style.height = Math.ceil(body.getBoundingClientRect().height + border) + 'px';
    });
    observer.observe(body);
  });
});
</script>