2

As you know, the <canvas> element in HTML5 has an internal size, set via elm.width and elm.height where elm is the DOM element.

The element within the DOM itself can have a different box size, set via CSS.

When the two differ, the browser automatically stretches the contents of the <canvas> to fit the content box size. Can this behaviour be adjusted without an extra element? Can it be set to something like background-size: contain so that it keeps the aspect ratio of the inner canvas size?

As far as I know, there is no standard way to do this, so webkit-specific hacks are also accepted. But if there is no way to do it at all, what's the most elegant to do it with a wrapper element (we all know that we can wrap the element and adjust it via JS on resize)?

The one solution I can think of is actually using background-size: contain and background: element() but this is Firefox-specific, and I am looking for a WebKit solution.

Side question, can a difference in element box and canvas internal sizes impact performance? If so, how much?

cb4
  • 6,689
  • 7
  • 45
  • 57
Ivo
  • 1,673
  • 3
  • 19
  • 28

1 Answers1

2

Yes, there is a new CSS property called object-fit. This allow you to set the content to for example cover which will then force aspect ratio to be kept at the same time as filling the DOM box.

Simply add this rule to the canvas element:

canvas {
  object-fit: cover;
  }

snapshot

Support

(Updated) It is (partially) supported in Safari, but not in IE. Opera Mini needs a -o- prefix.

Does not seem to work with WebGL canvas (or video) in Chrome likely related to these issues: issue #1, issue #2, issue #3.

As a fallback the size can be calculated manually. This can be used with drawImage. If drawImage is not an option it will require a wrapper element with overflow set to hidden to work.

Example

Both canvas' below are using the default bitmap size of 300x150. They are then stretched by defining CSS size 300x300 pixels.

Normally this would make the circle an oval. However, the canvas on the right will use the new object-fit rule set to cover, the bitmap will be scaled considering aspect ratio so we still get a circle, but of course we'll also loose some of the edges (as well as sharpness).

var ctx1 = document.getElementById("c1").getContext("2d");
var ctx2 = document.getElementById("c2").getContext("2d");

drawCircle(ctx1, 150, 75, 70);   // draw circle to stretched canvas 1
drawCircle(ctx2, 150, 75, 70);   // draw circle to object-fit/cover canvas 2

function drawCircle(ctx, x, y, r) {
  ctx.moveTo(x + r, y);
  ctx.arc(x, y, 70, 0 , 6.28);
  ctx.closePath();
  ctx.stroke();
}
#c1, #c2 {
    width:300px;
    height:300px;
    }
#c2 {
    -o-object-fit: cover;  /* for Opera Mini */
    object-fit: cover;     /* W3C version (incl. FF/webkit) */
    }
<canvas id="c1"></canvas><canvas id="c2"></canvas>
Community
  • 1
  • 1
  • Very nice. Unfortunately, and somewhat inconsistently, it seems to not work with GL context under Chrome. I'll make sure about this again and update my question – Ivo May 27 '15 at 16:33
  • @lwo hm, good to know. It could be a current bug/limitation in support with Chrome at the moment. I would report it to crbug.com with the WebGL case. –  May 27 '15 at 16:37
  • Yes, let's make sure it's not something I'm doing wrong first. I noticed you did not set canvas.width, canvas.height in your example, meanwhile in my use case it's set. – Ivo May 27 '15 at 16:43
  • @lvo if not set it will use the default size of 300x150 which was intentional. I updated with some issue links for Chrome. Seem to have problems at several areas with object-fit. I made a solution for manually calculating "cover" mode [here](http://stackoverflow.com/questions/21961839/simulation-background-size-cover-in-canvas/21961894#21961894) which may be useful as a fallback (?) –  May 27 '15 at 16:49
  • No, not really directly, since the use case is drawing a texture with CLAMP_TO_EDGE parameters, meaning it would stretch over the whole canvas internal size. The best solution for now is to use your logic, adapted to "contain", but on the DOM element itself and re-calculate on resize. – Ivo May 28 '15 at 10:49