Skip to content

Support multiple layers per <Canvas> - #424

Draft
mhkeller wants to merge 4 commits into
svelte-5-layercake-lib-v2from
canvas-draw
Draft

Support multiple layers per <Canvas>#424
mhkeller wants to merge 4 commits into
svelte-5-layercake-lib-v2from
canvas-draw

Conversation

@mhkeller

@mhkeller mhkeller commented Aug 23, 2026

Copy link
Copy Markdown
Owner

<Canvas> now owns its canvas. The element covers the whole chart container and the layout scales, clears and moves the origin before every repaint. Much inspired by @techniq's work in LayerChart and this ancient conversation. Components stop managing the canvas and hand over a draw function instead:

const k = getLayerCakeContext();
const canvas = getCanvasContext();

canvas.draw(ctx => {
	for (const d of k.data) {
		ctx.beginPath();
		ctx.arc(k.xGet(d), k.yGet(d), 5, 0, 2 * Math.PI);
		ctx.fill();
	}
});

Whatever the function reads (props, $state, k.*) triggers a repaint. Several components can share one <Canvas> and paint in order, so the old one-<Canvas>-per-component workaround (#50) is gone. Since the origin moves by the padding, canvas drawings can reach into the margins the way Svg and Html children always could.

API

  • getCanvasContext() returns a typed context: draw(fn), redraw() for things Svelte can't track and a read-only ctx
  • k.pointer(event) gives chart-area [x, y] on any layer. Layers cover different boxes now, so offsetX shifts meaning depending on where you listen. k.pointer doesn't
  • overflow on <Canvas>, the same prop as the other layouts. "hidden" clips at the chart area
  • draw() must be called during component setup and throws a clear error anywhere else
  • a one-time console warning when something else resizes the canvas (a v10-style scaleCanvas call, usually)

Also in here

  • the WebGL element no longer spills past the container by the padding. Its CSS was over-constrained
  • main's idempotent scaleCanvas and its test, brought forward so the merge is clean
  • the paint pass lives in helpers/paintLayers.js with unit tests for order, clipping and error isolation
  • the three canvas docs components rewritten. Guide and CHANGELOG updated
  • MapLayered puts two components on one canvas: a dot for each state too small to label
  • e2e checks: the canvas covers the container, canvas points land on the Svg points, layers paint in order

Worth knowing

  • A layer remounted by {#if} goes to the top of the stack. An order option can come later but could be clunky.
  • The full WebGL version of this pattern needs its own design (no translate in GL) and stays on the roadmap

@mhkeller mhkeller changed the title canvas owns the frame: one draw() pattern Support multiple layers per <Canvas> Aug 23, 2026
The <canvas> element now covers the whole chart container and the layout
scales, clears and translates before every repaint. Components hand
getCanvasContext().draw() a function instead of managing the canvas
themselves, so several can share one <Canvas>, repaint together and draw
into the margins like Svg and Html children can.

- getCanvasContext() with draw(fn), redraw() and a read-only ctx
- overflow prop on <Canvas>, matching the other layouts
- paint pass in helpers/paintLayers.js with unit tests
- main's idempotent scaleCanvas and its test brought forward
- canvas docs components, guide and changelog rewritten for the pattern
- MapLayered draws two components on one canvas
- e2e: full-bleed geometry, translate vs the Svg layer, layer order
@mhkeller mhkeller linked an issue Aug 23, 2026 that may be closed by this pull request
@mhkeller
mhkeller requested a balanced review from Copilot August 23, 2026 19:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Layer exceptions can leak canvas save-stack state, and direct buffer resizes bypass the new warning.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Makes <Canvas> own rendering and support multiple ordered drawing components.

Changes:

  • Adds getCanvasContext(), coordinated repainting, clipping, and pointer coordinates.
  • Migrates canvas components and expands the layered-map example.
  • Adds unit, layout, and end-to-end coverage plus documentation.
File summaries
File Description
src/lib/layouts/Canvas.svelte Manages canvas layers and repainting.
src/lib/helpers/paintLayers.js Paints ordered, isolated layers.
src/lib/lib/scaleCanvas.js Makes scaling idempotent.
src/lib/context.js Adds canvas and pointer APIs.
src/lib/LayerCake.svelte Implements pointer coordinates.
src/lib/index.js Exports canvas APIs and types.
src/lib/layouts/Webgl.svelte Corrects padded sizing.
src/_components/Scatter.canvas.svelte Migrates scatter drawing.
src/_components/Map.canvas.svelte Migrates map drawing.
src/_components/MapPoints.canvas.svelte Migrates point drawing.
src/routes/_examples/MapLayered.svelte Demonstrates shared canvas layers.
src/routes/_examples_ssr/MapLayered.svelte Updates the SSR example.
test/lib/scaleCanvas.test.js Tests scaling behavior.
test/lib/paintLayers.test.js Tests paint ordering and clipping.
test/e2e/canvas-layout.spec.js Verifies browser layout and pixels.
test/e2e/copy-clipboard.spec.js Serializes clipboard tests.
src/content/guide/99-helper-functions.md Updates scaling guidance.
src/content/guide/06-layout-component-props.md Documents canvas overflow.
src/content/guide/05-layout-components.md Documents the drawing API.
src/content/guide/04-computed-context-values.md Documents k.pointer.
src/content/guide/03-layercake-props.md Lists canvas types.
src/content/examples/MapLayered.md Describes layered canvas usage.
CHANGELOG.md Records API and migration changes.
Review details
  • Files reviewed: 23/27 changed files
  • Comments generated: 3
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

// A layer gets destroyed when its component is destroyed. Svelte only
// lets us hook into a component's destruction while it is setting up.
// So in order to ride that lifecycle, we have to require canvas.draw()
// to be called from inside an effect.
// stay blank.
console.error('[LayerCake] A canvas draw function threw an error:', err);
} finally {
ctx.restore();
Comment on lines +115 to +131
const sizeNow = canvas.style.width + ' ' + canvas.style.height;
if (sizeWeSet && sizeNow !== sizeWeSet && !warnedAboutResize) {
warnedAboutResize = true;
console.warn(
'[LayerCake] Something resized this <Canvas> between repaints, most likely a component calling scaleCanvas() itself. <Canvas> sizes and clears the canvas for you: draw through getCanvasContext().draw() and remove your scaleCanvas and clearRect calls. https://layercake.graphics/guide#canvas'
);
}

paintLayers(context, layers, {
containerWidth: k.containerWidth,
containerHeight: k.containerHeight,
width: k.width,
height: k.height,
padding: k.padding,
overflow
});
sizeWeSet = canvas.style.width + ' ' + canvas.style.height;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Canvas & Svelte 5 infinite effect loops

2 participants