241

Is it possible to fix the width and height of an HTML5 canvas element?

The usual way is the following :

<canvas id="canvas" width="300" height="300"></canvas>
pants
  • 192
  • 13
Dustin
  • 2,465
  • 3
  • 16
  • 5

3 Answers3

462

The canvas DOM element has .height and .width properties that correspond to the height="…" and width="…" attributes. Set them to numeric values in JavaScript code to resize your canvas. For example:

var canvas = document.getElementsByTagName('canvas')[0];
canvas.width  = 800;
canvas.height = 600;

Note that this clears the canvas, though you should follow with ctx.clearRect( 0, 0, ctx.canvas.width, ctx.canvas.height); to handle those browsers that don't fully clear the canvas. You'll need to redraw of any content you wanted displayed after the size change.

Note further that the height and width are the logical canvas dimensions used for drawing and are different from the style.height and style.width CSS attributes. If you don't set the CSS attributes, the intrinsic size of the canvas will be used as its display size; if you do set the CSS attributes, and they differ from the canvas dimensions, your content will be scaled in the browser. For example:

// Make a canvas that has a blurry pixelated zoom-in
// with each canvas pixel drawn showing as roughly 2x2 on screen
canvas.width  = 400;
canvas.height = 300; 
canvas.style.width  = '800px';
canvas.style.height = '600px';

See this live example of a canvas that is zoomed in by 4x.

var c = document.getElementsByTagName('canvas')[0];
var ctx = c.getContext('2d');
ctx.lineWidth   = 1;
ctx.strokeStyle = '#f00';
ctx.fillStyle   = '#eff';

ctx.fillRect(  10.5, 10.5, 20, 20 );
ctx.strokeRect( 10.5, 10.5, 20, 20 );
ctx.fillRect(   40, 10.5, 20, 20 );
ctx.strokeRect( 40, 10.5, 20, 20 );
ctx.fillRect(   70, 10, 20, 20 );
ctx.strokeRect( 70, 10, 20, 20 );

ctx.strokeStyle = '#fff';
ctx.strokeRect( 10.5, 10.5, 20, 20 );
ctx.strokeRect( 40, 10.5, 20, 20 );
ctx.strokeRect( 70, 10, 20, 20 );
body { background:#eee; margin:1em; text-align:center }
canvas { background:#fff; border:1px solid #ccc; width:400px; height:160px }
<canvas width="100" height="40"></canvas>
<p>Showing that re-drawing the same antialiased lines does not obliterate old antialiased lines.</p>
zett42
  • 25,437
  • 3
  • 35
  • 72
Phrogz
  • 296,393
  • 112
  • 651
  • 745
  • 2
    @Hazaart If you want to set them differently: `$('#mycanvas').attr({width:400,height:300}).css({width:'800px',height:'600px'});` If you want to have the visual size be the same as the pixel size, never set the styles, only the attributes. – Phrogz Nov 21 '13 at 13:54
  • 1
    "If you want to have the visual size be the same as the pixel size, never set the styles, only the attributes", is there a reason for this preference? If I have many objects in the canvas and I want to zoomin/zoomout, it would be much faster to just reset the css, no?(rather than loop through all objects)... – EnglishAdam Sep 11 '14 at 09:28
  • 3
    @Gamemorize: zooming via CSS makes it blurry. However, you can zoom via context scaling and translation between redrawing instead of changing the 'size' of each object. – Phrogz Sep 11 '14 at 13:01
  • 1
    Thx much appreciated. I see now what CSS is doing... it treats the canvas 'like' a image, scaling an image is obviously not as good as 're-drawing' it! – EnglishAdam Sep 11 '14 at 18:32
  • 3
    Also, you can now do "clear pixelated zoom-in" instead of "blurry pixelated zoom-in", at least on Chrome, using style "image-rendering:pixelated" on the canvas. I fiddled your fiddle to show the difference: http://jsfiddle.net/donhatch/9bheb/1663/ – Don Hatch Jan 25 '16 at 08:10
  • 1
    aha! +1 Thanks. In chrome, if you resize the canvas to be smaller, and do not call `clearRect`, then it appears as though the now-out-of-bounds areas that were drawn to when the canvas was larger are still there (at least this seems to be the case in my version when the `canvas.style.{width,heght}` are of static dimensions, but the `canvas.{width,height}` are shrunk without calling `clearRect`). I guess this is a good reason to not rely on setting `canvas.{width,height}` to "clear" the canvas, as you mentioned in your post. However, it seems weird to me that `clearRect` clears outside bounds – dylnmc Nov 17 '17 at 15:53
106

A canvas has 2 sizes, the dimension of the pixels in the canvas (it's backingstore or drawingBuffer) and the display size. The number of pixels is set using the the canvas attributes. In HTML

<canvas width="400" height="300"></canvas>

Or in JavaScript

someCanvasElement.width = 400;
someCanvasElement.height = 300;

Separate from that are the canvas's CSS style width and height

In CSS

canvas {  /* or some other selector */
   width: 500px;
   height: 400px;
}

Or in JavaScript

canvas.style.width = "500px";
canvas.style.height = "400px";

The arguably best way to make a canvas 1x1 pixels is to ALWAYS USE CSS to choose the size then write a tiny bit of JavaScript to make the number of pixels match that size.

function resizeCanvasToDisplaySize(canvas) {
   // look up the size the canvas is being displayed
   const width = canvas.clientWidth;
   const height = canvas.clientHeight;

   // If it's resolution does not match change it
   if (canvas.width !== width || canvas.height !== height) {
     canvas.width = width;
     canvas.height = height;
     return true;
   }

   return false;
}

Why is this the best way? Because it works in most cases without having to change any code.

Here's a full window canvas:

const ctx = document.querySelector("#c").getContext("2d");

function render(time) {
  time *= 0.001;
  resizeCanvasToDisplaySize(ctx.canvas);
 
  ctx.fillStyle = "#DDE";
  ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  ctx.save();
 
  const spacing = 64;
  const size = 48;
  const across = ctx.canvas.width / spacing + 1;
  const down = ctx.canvas.height / spacing + 1;
  const s = Math.sin(time);
  const c = Math.cos(time);
  for (let y = 0; y < down; ++y) {
    for (let x = 0; x < across; ++x) {
      ctx.setTransform(c, -s, s, c, x * spacing, y * spacing);
      ctx.strokeRect(-size / 2, -size / 2, size, size);
    }
  }
  
  ctx.restore();
  
  requestAnimationFrame(render);
}
requestAnimationFrame(render);

function resizeCanvasToDisplaySize(canvas) {
   // look up the size the canvas is being displayed
   const width = canvas.clientWidth;
   const height = canvas.clientHeight;

   // If it's resolution does not match change it
   if (canvas.width !== width || canvas.height !== height) {
     canvas.width = width;
     canvas.height = height;
     return true;
   }

   return false;
}
body { margin: 0; }
canvas { display: block; width: 100vw; height: 100vh; }
<canvas id="c"></canvas>

And Here's a canvas as a float in a paragraph

const ctx = document.querySelector("#c").getContext("2d");

function render(time) {
  time *= 0.001;
  resizeCanvasToDisplaySize(ctx.canvas);
 
  ctx.fillStyle = "#DDE";
  ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  ctx.save();
 
  const spacing = 64;
  const size = 48;
  const across = ctx.canvas.width  / spacing + 1;
  const down   = ctx.canvas.height / spacing + 1;
  const s = Math.sin(time);
  const c = Math.cos(time);
  for (let y = 0; y <= down; ++y) {
    for (let x = 0; x <= across; ++x) {
      ctx.setTransform(c, -s, s, c, x * spacing, y * spacing);
      ctx.strokeRect(-size / 2, -size / 2, size, size);
    }
  }
  
  ctx.restore();
  
  requestAnimationFrame(render);
}
requestAnimationFrame(render);

function resizeCanvasToDisplaySize(canvas) {
   // look up the size the canvas is being displayed
   const width = canvas.clientWidth;
   const height = canvas.clientHeight;

   // If it's resolution does not match change it
   if (canvas.width !== width || canvas.height !== height) {
     canvas.width = width;
     canvas.height = height;
     return true;
   }

   return false;
}
span { 
   width: 250px; 
   height: 100px; 
   float: left; 
   padding: 1em 1em 1em 0;
   display: inline-block;
}
canvas {
   width: 100%;
   height: 100%;
}
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent cursus venenatis metus. Mauris ac nibh at odio scelerisque scelerisque. Donec ut enim <span class="diagram"><canvas id="c"></canvas></span>
vel urna gravida imperdiet id ac odio. Aenean congue hendrerit eros id facilisis. In vitae leo ullamcorper, aliquet leo a, vehicula magna. Proin sollicitudin vestibulum aliquet. Sed et varius justo.
<br/><br/>
Quisque tempor metus in porttitor placerat. Nulla vehicula sem nec ipsum commodo, at tincidunt orci porttitor. Duis porttitor egestas dui eu viverra. Sed et ipsum eget odio pharetra semper. Integer tempor orci quam, eget aliquet velit consectetur sit amet. Maecenas maximus placerat arcu in varius. Morbi semper, quam a ullamcorper interdum, augue nisl sagittis urna, sed pharetra lectus ex nec elit. Nullam viverra lacinia tellus, bibendum maximus nisl dictum id. Phasellus mauris quam, rutrum ut congue non, hendrerit sollicitudin urna.
</p>

Here's a canvas in a sizable control panel

const ctx = document.querySelector("#c").getContext("2d");

function render(time) {
  time *= 0.001;
  resizeCanvasToDisplaySize(ctx.canvas);

  ctx.fillStyle = "#DDE";
  ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  ctx.save();
 
  const spacing = 64;
  const size = 48;
  const across = ctx.canvas.width / spacing + 1;
  const down = ctx.canvas.height / spacing + 1;
  const s = Math.sin(time);
  const c = Math.cos(time);
  for (let y = 0; y < down; ++y) {
    for (let x = 0; x < across; ++x) {
      ctx.setTransform(c, -s, s, c, x * spacing, y * spacing);
      ctx.strokeRect(-size / 2, -size / 2, size, size);
    }
  }
  
  ctx.restore();
  
  requestAnimationFrame(render);
}
requestAnimationFrame(render);

function resizeCanvasToDisplaySize(canvas) {
   // look up the size the canvas is being displayed
   const width = canvas.clientWidth;
   const height = canvas.clientHeight;

   // If it's resolution does not match change it
   if (canvas.width !== width || canvas.height !== height) {
     canvas.width = width;
     canvas.height = height;
     return true;
   }

   return false;
}

// ----- the code above related to the canvas does not change ----
// ---- the code below is related to the slider ----
const $ = document.querySelector.bind(document);
const left = $(".left");
const slider = $(".slider");
let dragging;
let lastX;
let startWidth;

slider.addEventListener('mousedown', e => {
 lastX = e.pageX;
 dragging = true;
});

window.addEventListener('mouseup', e => {
 dragging = false;
});

window.addEventListener('mousemove', e => {
  if (dragging) {
    const deltaX = e.pageX - lastX;
    left.style.width = left.clientWidth + deltaX + "px";
    lastX = e.pageX;
  }
});
body { 
  margin: 0;
}
.frame {
  display: flex;
  align-items: space-between;
  height: 100vh;
}
.left {
  width: 70%;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
}  
canvas {
  width: 100%;
  height: 100%;
}
pre {
  padding: 1em;
}
.slider {
  width: 10px;
  background: #000;
}
.right {
  flex 1 1 auto;
}
<div class="frame">
  <div class="left">
     <canvas id="c"></canvas>
  </div>
  <div class="slider">
  
  </div>
  <div class="right">
     <pre>
* controls
* go 
* here

&lt;- drag this
     </pre>
  </div>
</div>

here's a canvas as a background

const ctx = document.querySelector("#c").getContext("2d");

function render(time) {
  time *= 0.001;
  resizeCanvasToDisplaySize(ctx.canvas);
 
  ctx.fillStyle = "#DDE";
  ctx.fillRect(0, 0, ctx.canvas.width, ctx.canvas.height);
  ctx.save();
 
  const spacing = 64;
  const size = 48;
  const across = ctx.canvas.width / spacing + 1;
  const down = ctx.canvas.height / spacing + 1;
  const s = Math.sin(time);
  const c = Math.cos(time);
  for (let y = 0; y < down; ++y) {
    for (let x = 0; x < across; ++x) {
      ctx.setTransform(c, -s, s, c, x * spacing, y * spacing);
      ctx.strokeRect(-size / 2, -size / 2, size, size);
    }
  }
  
  ctx.restore();
  
  requestAnimationFrame(render);
}
requestAnimationFrame(render);

function resizeCanvasToDisplaySize(canvas) {
   // look up the size the canvas is being displayed
   const width = canvas.clientWidth;
   const height = canvas.clientHeight;

   // If it's resolution does not match change it
   if (canvas.width !== width || canvas.height !== height) {
     canvas.width = width;
     canvas.height = height;
     return true;
   }

   return false;
}
body { margin: 0; }
canvas { 
  display: block; 
  width: 100vw; 
  height: 100vh;  
  position: fixed;
}
#content {
  position: absolute;
  margin: 0 1em;
  font-size: xx-large;
  font-family: sans-serif;
  font-weight: bold;
  text-shadow: 2px  2px 0 #FFF, 
              -2px -2px 0 #FFF,
              -2px  2px 0 #FFF,
               2px -2px 0 #FFF;
}
<canvas id="c"></canvas>
<div id="content">
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Praesent cursus venenatis metus. Mauris ac nibh at odio scelerisque scelerisque. Donec ut enim vel urna gravida imperdiet id ac odio. Aenean congue hendrerit eros id facilisis. In vitae leo ullamcorper, aliquet leo a, vehicula magna. Proin sollicitudin vestibulum aliquet. Sed et varius justo.
</p>
<p>
Quisque tempor metus in porttitor placerat. Nulla vehicula sem nec ipsum commodo, at tincidunt orci porttitor. Duis porttitor egestas dui eu viverra. Sed et ipsum eget odio pharetra semper. Integer tempor orci quam, eget aliquet velit consectetur sit amet. Maecenas maximus placerat arcu in varius. Morbi semper, quam a ullamcorper interdum, augue nisl sagittis urna, sed pharetra lectus ex nec elit. Nullam viverra lacinia tellus, bibendum maximus nisl dictum id. Phasellus mauris quam, rutrum ut congue non, hendrerit sollicitudin urna.
</p>
</div>

Because I didn't set the attributes the only thing that changed in each sample is the CSS (as far as the canvas is concerned)

Notes:

  • Don't put borders or padding on a canvas element. Computing the size to subtract from the number of dimensions of the element is troublesome
gman
  • 100,619
  • 31
  • 269
  • 393
  • Always matching the canvas dimensions with style dimensions is NOT a good idea for HiDPI displays, because on such systems, more than 1 canvas pixel fits into every CSS px. – LubosD Nov 11 '20 at 16:35
  • 1
    Blindly setting your canvas to match HiDPI displays is bad because it will use a ton of memory and be many times slower to render. You should make a thoughtful choice when to make your canvas match HiDPI and when not. For most WebGL apps, not matching is better than matching otherwise your performance will be really poor. Especially on the average user's laptop with integrated GPU. For a diagram or map where that resolution is more important and speed is less important only then is it a good time to match HiDPI. Either that or give the user a choice (like most PC games do) – gman Nov 12 '20 at 05:24
  • that's a FULL answer. – oceanGermanique Apr 13 '23 at 03:09
28

Thank you very much! Finally I solved the blurred pixels problem with this code:

<canvas id="graph" width=326 height=240 style='width:326px;height:240px'></canvas>

With the addition of the 'half-pixel' does the trick to unblur lines.

PartTimeNerd
  • 305
  • 1
  • 2
  • 20
user1463699
  • 281
  • 3
  • 2
  • 28
    @UpTheCreek If you draw a line in canvas, it looks ticker then it should, as if it's blurred. By putting the lines on half pixels (say, 50.5 instead of 50) you get a nice, clean line. This is often done using ctx.translate(0.5, 0.5) at the very beginning of your code so you can forget about it afterwards – Johan Sep 06 '12 at 11:25