最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

javascript - canvas.getContext is not a function, when called on canvas - Stack Overflow

programmeradmin5浏览0评论

I am encountering an error where getContext cannot be called even though the element is a canvas.

var canvas = document.getElementById(id);
console.log(canvas, canvas.nodeName);
var ctx = canvas.getContext('2d');

Thanks!

Snippet of it working in isolation, but not in the script

var canvas = document.getElementById( '0_0' );
document.write(canvas.getContext('2d'));
<g class="node" transform="translate(210,330)">
  <canvas x="-8" y="-8" id="0_0"></canvas>
</g>

I am encountering an error where getContext cannot be called even though the element is a canvas.

var canvas = document.getElementById(id);
console.log(canvas, canvas.nodeName);
var ctx = canvas.getContext('2d');

Thanks!

Snippet of it working in isolation, but not in the script

var canvas = document.getElementById( '0_0' );
document.write(canvas.getContext('2d'));
<g class="node" transform="translate(210,330)">
  <canvas x="-8" y="-8" id="0_0"></canvas>
</g>

<canvas x=​"-8" y=​"-8" id=​"0_0">​ "canvas"
Uncaught TypeError: canvas.getContext is not a function
  at Array.populateNodes (script.js:95)
  at Array.Co.call (d3.v3.min.js:3)
  at gen_graph (script.js:63)
  at Object.success (main.js:16)
  at i (jquery.min.js:2)
  at Object.fireWith [as resolveWith] (jquery.min.js:2)
  at A (jquery.min.js:4)
  at XMLHttpRequest.<anonymous> (jquery.min.js:4)

The js, using d3:

Apologies for how bad my JS may or not be, I am quite new to it

Share Improve this question edited Feb 14, 2017 at 11:44 fishert asked Feb 12, 2017 at 23:01 fishertfishert 631 gold badge1 silver badge5 bronze badges 8
  • Post the output of your console here. Don't link to images on SO. – jmargolisvt Commented Feb 12, 2017 at 23:02
  • @jmargolisvt I don't think he can post images directly with that low rep. And I think posting the output as text will be a labor work! – ibrahim mahrir Commented Feb 12, 2017 at 23:06
  • Are you using jQuery to get the canvas element? Or are you using getElementById? – ibrahim mahrir Commented Feb 12, 2017 at 23:28
  • @ibrahimmahrir i am using getElementById, however using jQ gave the same result – fishert Commented Feb 12, 2017 at 23:34
  • Can you post all of script.js? – MultiplyByZer0 Commented Feb 12, 2017 at 23:44
 |  Show 3 more ments

3 Answers 3

Reset to default 10

I think the "canvas" element is treated as unknown "canvas" element of SVG by d3. So the "canvas" element is not mapped to HTMLCanvasElement but SVGUnknownElement in domtree of document, thus getContext() of SVGUnknownElement is undefined.

To solve this problem, you should wrap the "canvas" element by foreignObject element and add xhtml namespace to the "canvas" element.

I'm not good at d3, please try to construct this structure by using d3.

<g class="node" transform="translate(210,330)">
  <foreignObject x="-8" y="-8">
    <canvas id="0_0" xmlns="http://www.w3/1999/xhtml"></canvas>
  </foreignObject>
</g>

Or use image element instead of "canvas" element to put image created by (html)canvas element.

SVG structure

<g class="node" transform="translate(210,330)">
  <image x="-8" y="-8" id="0_0"/>
</g>

Javascript code

//create canvas element.
//var canvas = document.getElementById(nodes[i].__data__.name);
var canvas = document.createElement("canvas");
//console.log(canvas, canvas.nodeName);
var ctx = canvas.getContext('2d');

canvas.width = width;
canvas.height = height;

var idata = ctx.createImageData(width, height);
idata.data.set(buffer);
ctx.putImageData(idata, 0, 0);

//set image created by canvas to image element.
var image = document.getElementById(nodes[i].__data__.name);
image.width.baseVal.value = width;
image.height.baseVal.value = height;
image.href.baseVal = canvas.toDataURL();

With D3.js it's crucial to create the canvas element within the proper namespace by using .create('xhtml:canvas') or .append('xhtml:canvas'). The xmlns attribute is nice to have, but will be disregarded by most modern HTML5 browsers.

Here's a full D3.js example:

const svg = d3.select('body').append('svg')
  .style('width', '100%')
  .style('height', '100%');

const group = svg.append('g')
  .attr('class', 'node')
  .attr('transform', 'translate(10,10)');

const foreignObject = group.append('foreignObject')
  .attr('width', 100)
  .attr('height', 100);

const canvas = foreignObject.append('xhtml:canvas')
  .attr('xmlns', 'http://www.w3/1999/xhtml');

const context = canvas.node().getContext('2d');

console.log(canvas.node().constructor.name);
// => HTMLCanvasElement

// Draw something
context.fillStyle = 'blue';
context.fillRect(0, 0, 100, 100);
<script src="https://cdnjs.cloudflare./ajax/libs/d3/5.7.0/d3.min.js"></script>

The concept of namespaces it not easy to grasp and most time you won't need to explicitly set them, since they are inherited from the parent element. Mike Bostock explains the concept quite well in this Github issue.

(Yeah, I open the issue and it didn't know how to use namespaces either.)

I actually got something like this working just now, using d3 v5. I am doing this in typescript, so that took some figuring.

One problem is that if you just append an element the normal d3 way (selection.append()) you get a selection of a more generic type. You can't just fix this with casting. What you have to do is create it in the DOM using document.createElement() then call appendChild() to place it. When you do that you get a canvas that is of type HTMLCanvasElement.

One trick I didn't realize is you must specify the dimensions both in foreignObject and canvas or it will be clipped.

I am in angular land so it looks like this

import * as d3 from 'd3';
   ...
private svg: d3.Selection<Element, any, any, any>
private canvasNode: HTMLCanvasElement

constructor(private root: ElementRef) {}

let foreigner = this.svg.append("foreignObject")
foreigner.attr("width", "800")
foreigner.attr("height", "600")

let canvas = document.createElement("canvas")
canvas.setAttribute('xmlns', 'http://www.w3/1999/xhtml')
canvas.textContent = 'Drawing canvas is not supported'

this.canvasNode = foreigner.node().appendChild(canvas)
this.canvasNode.setAttribute("width", "800")
this.canvasNode.setAttribute("height", "600")

My ponent implements AfterViewInit so that I can delay the actual drawing (though you don't necessarily have to do this):

ngAfterViewInit(): void {
  let ctx: CanvasRenderingContext2D = this.canvasNode.getContext('2d')
  this.draw(ctx)
}

Finally, you have something you can draw on:

draw(ctx: CanvasRenderingContext2D): void {
  ctx.save()

  ctx.strokeStyle = "#FF0000"
  ctx.fillStyle = "#00FFFF"

  ctx.moveTo(0,0)
  ctx.lineTo(2000, 1000);
  ctx.stroke();

  ctx.restore()

  console.log("drawn")
}

I've been working with angular and d3 (in typescript) for some time now. Sometimes getting all the types correct is not easy, but it's worth the effort.

Hopefully this is helpful to someone.

发布评论

评论列表(0)

  1. 暂无评论