<div class="wrapper">
  <canvas id="canvas" width="100%" height="100%"></canvas>
</div>
body {
  margin: 0;
  padding: 0;
  height: 100vh;
  position: relative;
  overflow: hidden;
  cursor: zoom-in;
}

.wrapper {
  position: relative;
}
import {
  createArtboard,
  mouse,
  wheel,
  touch,
  raf,
  sticky,
  clickZoom
} from "https://esm.sh/artboard-deluxe";

const canvas = document.getElementById("canvas");
const artboard = createArtboard(
  canvas,
  [
    wheel({
      useMomentumScroll: true,
      useMomentumZoom: true
    }),
    mouse(),
    touch(),
    clickZoom(),
    raf()
  ],
  {
    initTransform: {
      x: window.innerWidth / 2 - 200,
      y: window.innerHeight / 2 - 150,
      scale: 0.5
    },
    overscrollBounds: 190
  }
);

const plugin = artboard.addPlugin(
  sticky({
    target: { width: 140, height: 40 },
    position: "top-left",
    origin: "bottom-left",
    keepVisible: true
  })
);

artboard.setArtboardSize(800, 600);

function loop(time: number) {
  window.requestAnimationFrame(loop);
  const { offset, scale, artboardSize } = artboard.loop(time);
  if (!artboardSize) {
    return;
  }
  const width = window.innerWidth;
  const height = window.innerHeight;
  canvas.width = width;
  canvas.height = height;
  const ctx = canvas.getContext("2d");

  ctx.globalAlpha = 1;
  // Clear canvas.
  ctx.clearRect(0, 0, width, height);

  // Draw a grey background.
  ctx.fillStyle = "#bcbcbc";
  ctx.fillRect(0, 0, width, height);

  // Multiply the current scale with the width and height to get the "actual"
  // size of the artboard as it would appear.
  const artboardWidth = Math.round(artboardSize!.width * scale);
  const artboardHeight = Math.round(artboardSize!.height * scale);

  // Draw the artboard.
  ctx.fillStyle = "white";
  // The offset is always unscaled, so we can keep it like this.
  ctx.fillRect(offset.x, offset.y, artboardWidth, artboardHeight);
  ctx.strokeStyle = "black";
  ctx.lineWidth = 6;
  ctx.strokeRect(offset.x, offset.y, artboardWidth, artboardHeight);

  const circles = [
    "#dc2626",
    "#ea580c",
    "#eab308",
    "#84cc16",
    "#14b8a6",
    "#0284c7",
    "#c026d3",
    "#fca5a5"
  ];

  for (let i = 0; i < circles.length; i++) {
    ctx.fillStyle = circles[i];
    ctx.beginPath();
    const p = Math.sin(time / 500 + i * 92);
    const ay = Math.cos(time / 900 + i * 324 + Math.sin(time / 3245)) * scale;
    const ax = Math.sin(time / 300 + i + Math.cos(time / 2000)) * scale;
    const radius = (20 + ((p + 1) / 2) * 50) * scale;
    ctx.globalAlpha = (Math.sin(time / 500 + i * 1000) + 1) / 4 + 0.5;
    ctx.arc(
      offset.x + artboardWidth / 2 + ax * 200,
      offset.y + artboardHeight / 2 + ay * 180,
      radius,
      0,
      2 * Math.PI
    );
    ctx.fill();
  }

  const zoom =
    "Zoom: " +
    Math.round(scale * 100)
      .toString()
      .padStart(3, " ") +
    "%";
  ctx.globalAlpha = 1;
  const stickyRect = plugin.getRect();
  ctx.fillStyle = "black";
  ctx.fillRect(stickyRect.x, stickyRect.y, stickyRect.width, stickyRect.height);
  ctx.font = "20px monospace";
  ctx.fillStyle = "white";
  ctx.textRendering = "optimizeSpeed";
  ctx.fillText(zoom, stickyRect.x + 10, stickyRect.y + 27);
}

window.requestAnimationFrame(loop);
View Compiled

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.