🏠 back to Observable

What is the best way to overlay an SVG over a Canvas?


I’m trying to reproduce https://bl.ocks.org/travisdoesmath/c0d08396a0bf8d5de8519ded00bb7866 in a notebook, but I’m struggling to return a cell with the pendulums in an SVG on top of the trails in a canvas element


Nevermind, I got it working!


I have been having the same issue. Could you share your solution here so other people searching and finding this post (as I did) can see how to solve it?


I guess the notebook that @travisdoesmath ended up publishing was this one:

However, it looks like that notebook doesn’t actually have an SVG overlaid on top of the canvas – the pendula are just drawn using canvas commands.

Anyways, here’s a snippet extracted from a draft notebook of mine (using d3) where I did put SVG over a canvas. If you can’t get it work let me know and I’ll try to publish a working notebook example.

  const container = d3.select(DOM.element('div'));
  container.style("position", "relative");
  const canvas = container.append('canvas').node();
  const context = canvas.getContext('2d');
  canvas.width = 800;
  canvas.height = 600;

  const svg = container.append('svg')
    .attr("width", canvas.width)
    .attr("height", canvas.height)
    .style("position", "absolute")
    .style("top", '0px')
    .style("left", '0px');
  // put your canvas commands and SVG stuff here

  return container.node()


Another way to do it is to embed the canvas directly into the SVG using foreignObject: https://beta.observablehq.com/@mootari/embed-canvas-into-svg


This got me thinking – what would be the best way to save an SVG with an embedded canvas? I experimented with importing the Saving SVG functions into your notebook and they seem to ignore the canvas.


Chrome reports the canvas as being tainted, Firefox exports the plain canvas element. Apparently the case (canvas inside foreignObject) is not well defined in the specs (Chrome bug).

I’m thinking that you’d have to convert the canvas to an image with a data src, ideally replacing the whole foreignObject node.


I’ve added a download feature to my notebook. Also now included is a helper function that converts canvas elements to images with data URLs.

@mike How do you feel about a callback parameter in your SVG serialize() helper to handle these edge cases?


Is this a request for a lazy DOM.download, or something else?


Something else, cleaning up the cloned SVG (e.g. replacing canvas with img). In serialize() in https://beta.observablehq.com/@mbostock/saving-svg you’re already using a treewalker to sanitize URIs in the SVG. I was thinking perhaps a callback(currentNode, svg) could be added to allow custom modifications to the SVG prior to serialization.

Edit: I’m not sure how well TreeWalker handles live modifications to the target DOM though, and the above use case would also require receiving the matching element from the source SVG. If you give the idea a :+1: I’d be happy to look into it and create a merge request.


I’d definitely learn a lot from seeing how you’d do this, so let me put in a :+1: