Capturing Snapshots
Snapshots are great for capturing features of a simulation for detailed discussions, inclusion in homework problems or a book, or even sharing on social media.
WebGPU is an evolving framework, and currently image capture of a canvas only works while the animation is active.
Adding snapshots is a great opportunity to foray into custom elements1. This implementation
uses the CanvasSnapshooter
class to implement the snapshot process. This class listens for custom events, and when an appropriate
event is received, it generates a snapshot of the provided canvas. The custom element places a button
to generate these events. The loose coupling between the snapshooter and the button means the button can
be placed anywhere on the page.
The CanvasSnapshooter
constructor takes three arguments.
- snapShotContainer
- The parent HTML element for the snapshots. Snapshots are positioned within this element.
- The Canvas
- The canvas element we capture an image from via toDataURL.
- id
- The identifier for this particular canvas snapshooter instance. We listen for events with this target to trigger a snapshot.
const snapShotContainer = document.getElementById("snapshots")
const snapshooter = new vizit.lesson.CanvasSnapshooter(snapShotContainer, schrodingerRenderer.getCanvas(),
"schrodingerResults");
The canvas-snapshooter
element defines a button to trigger the snapshot. The
companion snapshot-caption
element defines the caption for that snapshot. Snapshots
are absolutely positioned within the snapShotContainer
. They can also be minimized,
and dragged to position them anywhere on the page.
We see how it all comes together in the figure at the top of the page. The schrodingerResults canvas is the display target for the simulation, and we pass that same canvas to the CanvasSnapshooter constructor, and even pass the same string, "schrodingerResults" as the id for the CanvasSnapshooter. We could use any string here, as long as the canvas-snapshooter target matches the CanvasSnapshooter id. I just wanted an id that reflected what we were capturing.
<figure class="center" id="snapshots">
<canvas width="800" height="100" id="schrodingerResults">
</canvas>
<div class="buttonContainer">
<button id="start">▶<br/>Play</button>
<button id="stop">■<br/>Stop</button>
<button data-bc="true" class="launcher">↺<br/>Restart</button>
<canvas-snapshooter target="schrodingerResults">
<snapshot-caption>Snapshot at time ${time}.</snapshot-caption>
</canvas-snapshooter>
</div>
<figcaption>
Now we can capture images of our simulations.
</figcaption>
</figure>
The caption is evaluated as a
JavaScript template
when each snapshot is generated. In this case we have a global variable, time
, on the page so
${time}
is replaced with the time
value for each snapshot. The caption identifies
how far into the simulation the image was captured.

We can clean this up a bit. Looking at the code, dt = 5.0E-07
, we see that
there are at most 6 decimal places of significance. Figures beyond this are simply numerical
noise from the imprecise representation of floating point numbers.
JavaScript templates let us embed any JavaScript expression, so we can use
${time.toFixed(6)}
to finesse the displayed time value.
<snapshot-caption>Snapshot at time ${time.toFixed(6)}.</snapshot-caption>

Buttons are losely coupled to the CanvasSnapshooter through JavaScript events, so we can place them anywhere on the page.
- Custom elements have been around for a while, but now they are broadly supported.
