Param Panel

Canvas

A first-class custom 2D canvas control that handles pixel ratio, resizing, and the animation-frame lifecycle for you.

The canvas control is one of ParamPanel's defining features. It lets you draw anything — a histogram, a waveform, a spectrum, a preview — while the library handles all the fiddly parts you would otherwise write by hand.

You never have to manage:

  • Device-pixel-ratio scaling
  • Canvas backing-store dimensions
  • ResizeObserver
  • Pausing while hidden
  • Animation-frame cleanup
  • Destruction cleanup

Try it

Usage

You provide a draw(ctx, size) callback. size gives you CSS-pixel dimensions, and the context is pre-scaled so you draw in CSS-pixel coordinates.

const histogram = pp.canvas({
  label: "Histogram",
  height: 80,
  draw(ctx, size) {
    ctx.clearRect(0, 0, size.width, size.height);
    drawHistogram(ctx, size.width, size.height, histogramData);
  },
});

// Ask for one redraw on the next animation frame.
histogram.invalidate();

Configuration

interface CanvasSize {
  width: number;
  height: number;
  pixelRatio: number;
}

type CanvasDrawCallback = (
  context: CanvasRenderingContext2D,
  size: CanvasSize,
) => void;

interface CanvasConfig extends BaseControlConfig {
  height?: number;

  /**
   * "invalidated" draws only when requested.
   * "continuous" draws every animation frame.
   */
  render?: "invalidated" | "continuous";

  draw: CanvasDrawCallback;
}

interface CanvasControl extends Control<CanvasConfig> {
  invalidate(): this;
  setContinuous(continuous: boolean): this;

  getCanvas(): HTMLCanvasElement;
  getContext(): CanvasRenderingContext2D;
}

Invalidated rendering (default)

By default render is "invalidated": the canvas only draws when you ask it to. Call invalidate() when your data changes, and one draw is scheduled on the next animation frame. Multiple invalidations in the same frame are coalesced into a single draw:

function onDataChanged() {
  updateData();
  histogram.invalidate(); // batched — at most one draw per frame
}

Continuous rendering

For animations, switch to continuous mode and the draw callback runs every frame:

histogram.setContinuous(true);
// or
histogram.configure({ render: "continuous" });

Continuous rendering automatically pauses when the control is hidden, its folder is collapsed, the panel is collapsed, the page is not visible (where practical), or the control is destroyed. You never leak an animation loop.

Pixel ratio

The draw callback receives CSS-pixel dimensions in size. Internally the library keeps the backing store sharp on high-DPI displays:

canvas.width === cssWidth * pixelRatio;
canvas.height === cssHeight * pixelRatio;

and pre-transforms the context so your drawing code stays in CSS pixels. If you need the raw device ratio, it is available as size.pixelRatio.

Direct access

For advanced cases you can reach the underlying element and context — though you rarely need to for ordinary drawing:

const element = histogram.getCanvas();
const ctx = histogram.getContext();

Full example: webcam luminance histogram

const histogram = pp.canvas({
  label: "Luminance histogram",
  height: 100,
  draw(ctx, size) {
    ctx.clearRect(0, 0, size.width, size.height);

    const max = Math.max(...bins, 1);
    const barWidth = size.width / bins.length;

    for (let i = 0; i < bins.length; i++) {
      const height = (bins[i] / max) * size.height;

      ctx.fillRect(
        i * barWidth,
        size.height - height,
        Math.ceil(barWidth),
        height,
      );
    }
  },
});

function processFrame() {
  updateHistogramBins();
  histogram.invalidate();
}

On this page