Panel Haus Journal
Docs Launch App ↗

The nine-layer canvas, and how a browser comic editor exports pixel-clean

An editor has to draw things the export must never contain. Panel Haus solves it with nine strictly ordered layers and a second canvas that the user never sees.

· Panel Haus


Every canvas editor has the same awkward problem: it must draw things on screen that must never appear in the output. Selection handles. Resize corners. The dotted box around whatever you clicked. Those are the interface, not the artwork, and a single one of them surviving into an exported page ruins it.

Panel Haus solves this with nine strictly ordered layers, two of which can never be exported because the export is rendered on a completely different canvas.

The nine layers

Order is everything here. Each layer is a separate canvas the browser composites in sequence, and the sequence is what makes a comic look like a comic.

zLayerWhat lives there
0backgroundPatterns and images behind everything
1panelsThe panel containers themselves
2imagesArtwork filling a panel, clipped to its bounds
3boundContentContent attached to a panel and clipped with it
4panelBordersThe composed borders, drawn above the art they frame
5contentFloating bubbles and stickers, above the borders
6globalOverlayAnything applied to every page at once
7transformerSelection and resize handles
8tailHandlesThe grips for dragging a speech bubble tail

Two of those choices are less obvious than they look, and both were bugs before they were decisions.

Borders sit above images, not below. An image fills a panel and is clipped to it. If the border drew first, antialiasing at the clip edge would leave a hairline of image on top of the frame. Drawing the border last covers the seam.

Floating content sits above the borders, but bound content sits below. That single split is what lets a sticker break out of its panel and overlap the frame like a comic actually does, while a caption anchored inside a panel stays properly inside it. Those are two different creative intentions and they need two different layers.

The layers that can never export

Layers 7 and 8 are pure interface. They exist so you can grab a bubble and drag its tail, and they must never reach a PNG.

The safe way to guarantee that is not to hide them at export time. Hiding is a step that can be forgotten, or that can fail halfway, and every canvas app that takes that route eventually ships a file with a selection box in it.

Instead, exporting builds an entirely separate canvas. A fresh stage, at the page's exact pixel dimensions, inside a container positioned at -9999px so it is never visible. The page is rendered into it from scratch, and the interface layers are simply never created there.

The result is that the export cannot contain a selection handle, in the same way a photograph of an empty room cannot contain a person who was never in it. It is not hidden. It was never drawn.

That also means exporting cannot disturb what you are looking at. The visible canvas is not resized, re-rendered, or touched at all, which matters because the alternative is a screen that flickers every time you export and occasionally does not come back the way it was.

Why speech bubble tails are SVG paths

A speech bubble tail is the little pointer aiming at whoever is talking. The easy way to draw one in a browser is a CSS triangle: a bordered element with zero width and height, a trick that has been in use for twenty years.

Panel Haus draws them as SVG paths on the canvas instead, which is more work.

The reason is export. A CSS triangle exists in the DOM, not on the canvas, so it appears on screen and vanishes from the exported image. Every workaround for that is worse than drawing the tail properly in the first place. So a tail is a real path, in the same coordinate space as the bubble, and it exports because it was always part of the picture rather than decoration layered over it.

Thought bubbles are the same idea, drawn as a trail of circles instead of a path.

Why a page's size is locked forever

When a page is created it takes a dimension, and that dimension never changes for the life of the page. Every position on it is stored as an absolute pixel offset from that fixed size.

That sounds inflexible, and it is, deliberately. The alternative is storing positions as percentages so a page can be resized later, and percentages mean every element shifts a fraction of a pixel every time anything is recalculated. Do it a few times and the lettering has drifted off the mouths.

Locking the dimension means a coordinate written today means exactly the same thing in a year. It is the same reason print production settles the trim size before anyone draws.

The visible cost is that you cannot convert a square page to a comic-book page after the fact. The invisible benefit is that nothing you place ever moves on its own.

The general lesson

If output correctness matters, do not build one pipeline and try to subtract the parts you do not want. Build a second pipeline that never had them.

Hiding is a step. Steps get skipped, fail halfway, and get refactored by someone who does not know why they were there. A separate render path has no step to skip, and the guarantee is structural rather than procedural.

That is worth more than it sounds when the output is the thing a person spent their weekend making.

Related

System Architecture covers how the app renders and stores. Export & Publishing covers the formats themselves.