Create a gist now

Instantly share code, notes, and snippets.

@mbostock /.block
Last active Jul 26, 2017

What would you like to do?
DOM-to-Canvas using D3
license: gpl-3.0

Mouseover to draw circles!

This is a quick proof-of-concept example demonstrating how to create a canvas scenegraph in the DOM using custom namespaced elements. The scenegraph in this example consists of a simple container sketch element and a number of child circle elements:

<custom:sketch width="960" height="500">
  <custom:circle x="300" y="400" r="128" strokeStyle="red"/>
  <custom:circle x="302" y="404" r="129" strokeStyle="red"/>

The browser ignores these elements because they exist in our "custom" namespace. To render them, we use a timer that iterates over the child elements and draws them to a canvas element.

Why do this? Well, if you wanted your own custom representation tailored to a specific application or domain, you can! This example demonstrates how to use the DOM to implement your own element hierarchy and render it to canvas. If you're designing a general-purpose graphical representation, though, I recommend using SVG instead. For comparison, see the original OMG Particles! example.

Implementation note: I'd prefer to use DOM Mutation Events to listen for changes to our custom elements, but browsers seem a bit sluggish in reporting them, particularly when elements are removed. If you have a slow-moving scene, you could probably get away with using mutation events rather than a timer that runs continuously. Alternatively, you could improve this example by stopping the timer after extended periods of activity.

<!DOCTYPE html>
<meta charset="utf-8">
<script src=""></script>
// Register the "custom" namespace prefix for our custom elements.
d3.ns.prefix.custom = "";
var width = 960,
height = 500;
// Add our "custom" sketch element to the body.
var sketch ="body").append("custom:sketch")
.attr("width", width)
.attr("height", height)
// On each mouse move, create a circle that increases in size and fades away."mousemove", function() {
.attr("x", d3.event.clientX)
.attr("y", d3.event.clientY)
.attr("radius", 0)
.attr("strokeStyle", "red")
.attr("radius", 200)
.attr("strokeStyle", "white")
function custom(selection) {
selection.each(function() {
var root = this,
canvas = root.parentNode.appendChild(document.createElement("canvas")),
context = canvas.getContext("2d"); = "absolute"; = root.offsetTop + "px"; = root.offsetLeft + "px";
// It'd be nice to use DOM Mutation Events here instead.
// However, they appear to arrive irregularly, causing choppy animation.
// Clear the canvas and then iterate over child elements.
function redraw() {
canvas.width = root.getAttribute("width");
canvas.height = root.getAttribute("height");
for (var child = root.firstChild; child; child = child.nextSibling) draw(child);
// For now we only support circles with strokeStyle.
// But you should imagine extending this to arbitrary shapes and groups!
function draw(element) {
switch (element.tagName) {
case "circle": {
context.strokeStyle = element.getAttribute("strokeStyle");
context.arc(element.getAttribute("x"), element.getAttribute("y"), element.getAttribute("radius"), 0, 2 * Math.PI);

Interesting . thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment