61

I have a canvas element defined statically in the html with a width and height. If I attempt to use JavaScript to resize it dynamically (setting a new width and height - either on the attributes of the canvas or via the style properties) I get the following error in Firefox:

uncaught exception: [Exception... "Illegal operation on WrappedNative prototype object" nsresult: "0x8057000c (NS_ERROR_XPC_BAD_OP_ON_WN_PROTO)" location: "JS frame :: file:///home/russh/Desktop/test.html :: onclick :: line 1" data: no]

Is it possible to resize this element or do I have to destroy it and create a new element on the fly?

russx2
  • 841
  • 1
  • 6
  • 7
  • Actually the error message looks as if you had tried to change the `width` or `height` property of `HTMLCanvasElement.prototype` directly. I have no clue how this could be done accidentally. – Robert Dec 23 '12 at 23:00
  • Take a look at my implementation: http://stackoverflow.com/a/23128583/1265753 – karaxuna Apr 17 '14 at 08:51

7 Answers7

76

You didn't publish your code, and I suspect you do something wrong. it is possible to change the size by assigning width and height attributes using numbers:

canvasNode.width  = 200; // in pixels
canvasNode.height = 100; // in pixels

At least it works for me. Make sure you don't assign strings (e.g., "2cm", "3in", or "2.5px"), and don't mess with styles.

Actually this is a publicly available knowledge — you can read all about it in the HTML canvas spec — it is very small and unusually informative. This is the whole DOM interface:

interface HTMLCanvasElement : HTMLElement {
           attribute unsigned long width;
           attribute unsigned long height;

  DOMString toDataURL();
  DOMString toDataURL(in DOMString type, [Variadic] in any args);

  DOMObject getContext(in DOMString contextId);
};

As you can see it defines 2 attributes width and height, and both of them are unsigned long.

Eugene Lazutkin
  • 43,776
  • 8
  • 49
  • 56
  • 41
    Note: when you change the canvas size it seems to clear the canvas (at least in chrome). – RobKohr Aug 31 '11 at 13:50
  • 5
    yes. I agree. Is there a way to resize the canvas while also resizing (scaling) the images that is inside it? – Alexis Aug 11 '12 at 03:30
  • It would be helpful to post an example usage of this on jsfiddle. – Anderson Green Dec 16 '12 at 20:12
  • 16
    @Alexis Changing it's style changes it's scaling. So canvas.style.width = "700px"; will change it's scaling but canvas.width = 700; will not. – Joseph Lennox Apr 29 '14 at 17:45
  • 1
    @JosephLennox That is super useful to know. You saved me a ton of mental effort by mentioning that. – Daniel Kaplan May 22 '14 at 01:51
  • 3
    Yes, thanks, @JosephLennox. In jquery, $('#canvas').width(500) will rescale, while $('#canvas')[0].width = 500 will not rescale. I was doing the former and would have taken forever solving it without your comment. – ChrisPhoenix Feb 20 '15 at 02:07
  • It not just resets the canvas, it does so asynchronously. So redrawing in the same function as the size change has no effect. The redraw code has to be wrapped in `setTimeout(() => { ... }, 0)`. This includes things like `lineWidth = ...`. – AndreKR May 18 '18 at 04:53
26

Note that if your canvas is statically declared you should use the width and height attributes, not the style, eg. this will work:

<canvas id="c" height="100" width="100" style="border:1px"></canvas>
<script>
    document.getElementById('c').width = 200;
</script>

But this will not work:

<canvas id="c" style="width: 100px; height: 100px; border:1px"></canvas>
<script>
    document.getElementById('c').width = 200;
</script>
sam hocevar
  • 11,853
  • 5
  • 49
  • 68
15

I just had the same problem as you, but found out about the toDataURL method, which proved useful.

The gist of it is to turn your current canvas into a dataURL, change your canvas size, and then draw what you had back into your canvas from the dataURL you saved.

So here's my code:

var oldCanvas = canvas.toDataURL("image/png");
var img = new Image();
img.src = oldCanvas;
img.onload = function (){
    canvas.height += 100;
    ctx.drawImage(img, 0, 0);
}
john
  • 167
  • 1
  • 2
  • 6
    ctx.getImageData() and ctx.putImageData() are lighter weight and designed for this task. – fuzzyTew Jul 28 '13 at 01:40
  • @fuzzyTew I'm pretty sure that using ```getImageData()``` and ```putImageData()``` will be efficient, but will copy pixel by pixel, thus not scaling the old image data, whereas using ```drawImage()``` would allow for scaling. – snapfractalpop Jun 21 '14 at 13:52
  • Why use `toDataUrl`? This causes unnecessary data conversion (raw=>png). You could use `drawImage` twice instead. – Tomáš Zato Nov 30 '14 at 20:42
5
<div id="canvasdiv" style="margin: 5px; height: 100%; width: 100%;">
    <canvas id="mycanvas" style="border: 1px solid red;"></canvas>
</div>
<script>
$(function(){
    InitContext();
});
function InitContext()
{
var $canvasDiv = $('#canvasdiv');

var canvas = document.getElementById("mycanvas");
canvas.height = $canvasDiv.innerHeight();
canvas.width = $canvasDiv.innerWidth();
}
</script>
Jignesh Variya
  • 1,869
  • 16
  • 12
4

This worked for me just now:

<canvas id="c" height="100" width="100" style="border:1px solid red"></canvas>
<script>
var c = document.getElementById('c');
alert(c.height + ' ' + c.width);
c.height = 200;
c.width = 200;
alert(c.height + ' ' + c.width);
</script>
Kent Brewster
  • 2,480
  • 2
  • 22
  • 27
1

Here's my effort to give a more complete answer (building on @john's answer).

The initial issue I encountered was changing the width and height of a canvas node (using styles), resulted in the contents just being "zoomed" or "shrunk." This was not the desired effect.

So, say you want to draw two rectangles of arbitrary size in a canvas that is 100px by 100px.

<canvas width="100" height="100"></canvas>

To ensure that the rectangles will not exceed the size of the canvas and therefore not be visible, you need to ensure that the canvas is big enough.

var $canvas = $('canvas'),
    oldCanvas,
    context = $canvas[0].getContext('2d');

function drawRects(x, y, width, height)
{
  if (($canvas.width() < x+width) || $canvas.height() < y+height)
  {
    oldCanvas = $canvas[0].toDataURL("image/png")
    $canvas[0].width = x+width;
    $canvas[0].height = y+height;

    var img = new Image();
    img.src = oldCanvas;
    img.onload = function (){
      context.drawImage(img, 0, 0);
    };
  }
  context.strokeRect(x, y, width, height);
}


drawRects(5,5, 10, 10);
drawRects(15,15, 20, 20);
drawRects(35,35, 40, 40);
drawRects(75, 75, 80, 80);

Finally, here's the jsfiddle for this: http://jsfiddle.net/Rka6D/4/ .

Gezim
  • 7,112
  • 10
  • 62
  • 98
0

Prototypes can be a hassle to work with, and from the _PROTO part of the error it appears your error is caused by, say, HTMLCanvasElement.prototype.width, possibly as an attempt to resize all the canvases at once.

As a suggestion, if you are trying to resize a number of canvases at once, you could try:

<canvas></canvas>
<canvas></canvas>
<canvas></canvas>
<script type="text/javascript">
    ...
</script>

In the JavaScript, instead of invoking a prototype, try this:

$$ = function(){
    return document.querySelectorAll.apply(document,arguments);
}
for(var i in $$('canvas')){
    canvas = $$('canvas')[i];
    canvas.width = canvas.width+100;
    canvas.height = canvas.height+100;
}

This would resize all the canvases by adding 100 px to their size, as is demonstrated in this example


Hope this helped.
Braden Best
  • 8,830
  • 3
  • 31
  • 43