Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Appending canvas to SVG breaks canvas element #236

Closed
stekhn opened this issue Nov 28, 2019 · 3 comments
Closed

Appending canvas to SVG breaks canvas element #236

stekhn opened this issue Nov 28, 2019 · 3 comments

Comments

@stekhn
Copy link

stekhn commented Nov 28, 2019

Appending a canvas node to a SVG node and trying to get a context results in an error message: Uncaught TypeError: canvas.node(...).getContext is not a function. This affects Chrome, Firefox and Safari. I didn't test other browsers.

Minimal example:

import { select } from 'd3-selection';

const svg = select('body').append('svg')
const canvas = svg.append('canvas');
// console.log(canvas.node().constructor.name)
const context = canvas.node().getContext('2d');

Apparently the canvas node gets created using the wrong constructor: SVGElement. Wrapping the canvas in a SVG foreignObject didn't fix the problem for me.

However, the canvas element works just fine when it is created using standard DOM API methods:

import { select } from 'd3-selection';

const svg = select('body').append('svg')
const canvas = svg.node().appendChild(document.createElement('canvas'))
// console.log(canvas.constructor.name)
const context = canvas.getContext('2d');

By doing so, the HTMLCanvasElement constructor is used to create the element, which seems to be correct.

I never encountered this problem with d3-select outside of SVG nodes and I'm not sure whether this combination of SVG and canvas is even compatible with any web standard. The SVG foreignObject allows you to embed elements from a different namespace, but the examples I've seen so far were mostly XHTML-compatible, which canvas isn't (?).

Still, most browsers care little about namespaces and will try to parse and render the content anyway. Having a canvas embed in SVG can be very convenient, especially when working with maps applications and d3-zoom.

Full demo: https://observablehq.com/@stekhn/d3-cant-create-canvas-element-within-svg-element

@mbostock
Copy link
Member

Please read the documentation for how namespaces work in D3. When you append an element and you don’t specify a namespace, it is inherited from the parent element. The only exception to this is elements whose name matches the namespace prefix, which in practice is just the svg element. So, if you want to add a canvas element to an svg element, you need to specify the namespace explicitly as xhtml:canvas.

@stekhn
Copy link
Author

stekhn commented Nov 28, 2019

Thanks for the quick help! I didn't find anything useful in the docs or on Stack Overflow.

For future reference, the right way to specify the namespace is:

const canvas = svg.append('xhtml:canvas');

It tried to solve the namespace problem with SVG attributes like xmlns="http://www.w3.org/1999/xhtml" and requiredExtensions="http://www.w3.org/1999/xhtml" without success.

@mbostock
Copy link
Member

The relevant documentation from the README is on selection.append:

The specified name may have a namespace prefix, such as svg:text to specify a text attribute in the SVG namespace. See namespaces for the map of supported namespaces; additional namespaces can be registered by adding to the map. If no namespace is specified, the namespace will be inherited from the parent element; or, if the name is one of the known prefixes, the corresponding namespace will be used (for example, svg implies svg:svg).

In particular, “If no namespace is specified, the namespace will be inherited from the parent element.” Meaning that your append was equivalent to svg:canvas, since you were appending to an svg:foreignObject.

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

No branches or pull requests

2 participants