I am used to working with SVGs. When you create a SVG element in a webpage, you can specify its viewbox by stating the minimum X,Y coordinate and the maximum X,Y coordinate. Eg: (0 0 100 100)
That way, the SVG container will responsively adapt to the page window size and you can still draw shapes according to that original coordinate system (0-100) without worrying about the relative screen size. So if you draw a rectangle at coordinate (50,50) you can be guaranteed that the rectangle will always appear in the center of the container.
But apparently the HTML canvas does not have an equivalent of a viewbox. It only takes in coordinates in pixels? This makes it very difficult to work in instances where you need a canvas to resize(adapt) to the screen size, because all your drawings will get displaced.
What's the solution to this?
I am used to working with SVGs. When you create a SVG element in a webpage, you can specify its viewbox by stating the minimum X,Y coordinate and the maximum X,Y coordinate. Eg: (0 0 100 100)
That way, the SVG container will responsively adapt to the page window size and you can still draw shapes according to that original coordinate system (0-100) without worrying about the relative screen size. So if you draw a rectangle at coordinate (50,50) you can be guaranteed that the rectangle will always appear in the center of the container.
But apparently the HTML canvas does not have an equivalent of a viewbox. It only takes in coordinates in pixels? This makes it very difficult to work in instances where you need a canvas to resize(adapt) to the screen size, because all your drawings will get displaced.
What's the solution to this?
Share Improve this question asked Mar 22 at 17:10 JackieChilesTheSecondJackieChilesTheSecond 1012 bronze badges 4 |2 Answers
Reset to default 3Transform matrix
When rendering to the 2D canvas context all coordinates are first transformed by the current transformation matrix.
You can manipulate the matrix with the following calls.
- ctx.getTransform
- ctx.setTransform overwrites existing transform.
- ctx.transform *
- ctx.scale *
- ctx.rotate *
- ctx.translate *
- ctx.resetTransform same as
ctx.setTransform(1,0,0,1,0,0)
sets identity matrix.
Note Functions marked with * multiply the existing matrix with a new matrix. IE if you call from default ctx.scale(2, 2); ctx.scale(2, 2)
. The resulting matrix will scale by 4, 4.
You can set a view box using the following function
function setViewBox(ctx, left, top, right, bottom) {
const viewXScale = ctx.canvas.width / (right - left);
const viewYScale = ctx.canvas.height / (bottom - top);
ctx.setTransform(
viewXScale, 0, // X Axis dir & scale in pixels
0, viewYScale, // Y Axis dir & scale in pixels
-left * viewXScale, -top * viewYScale // origin in pixels
);
}
Use as followed...
setViewBox(ctx, -50, -50, 50, 50);
ctx.arc(0, 0, 49, 0, Math.PI * 2); // arc in center of canvas
This ensures that ctx.lineWidth
and fonts are correctly scaled as well.
Note that a square aspect may not be maintained.
You can modify the transform to fit the view box within the canvas or fill the canvas with the view box.
In fact canvas
and svg
have a quite similar concept when it comes to defining the drawing area.
- you need to specify a "canvas/viewBox" size - otherwise you get the fallback 300x150px
- the specified "canvas/viewBox" size doesn't necessarily express the actual layout size – affected by CSS rules. But we need to be careful about not changing the aspect ratios
- we can't derive intrinsic dimensions from the "content" – as we can do for raster images which have a perfectly determined size due to their pixel data
A common practice would be to add width
and height
attributes to your canvas (or set these in your drawing function). These attributes would serve a similar purpose as a SVG viewBox
– you define an "artboard" size and most importantly an aspect-ratio.
let cx=100, cy=50, r=25, fill='green'
let canvas = document.getElementById('canvas')
let canvasAdaptiveRes = document.getElementById('canvasAdaptiveRes')
let canvasHires = document.getElementById('canvasHires')
let svg = document.getElementById('svg')
let circle = document.getElementById('circle')
// draw on canvas
drawCircle(canvas, cx, cy, r, fill);
drawCircleAdaptiveRes(canvasAdaptiveRes, cx, cy, r, fill);
drawCircleHires(canvasHires, cx, cy, r, fill);
// sync SVG
circle.setAttribute('cx', cx)
circle.setAttribute('cy', cy)
circle.setAttribute('r', r)
circle.setAttribute('fill', fill)
function drawCircle(canvas, cx, cy, r, fill){
const ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.arc(cx, cy, r, 0, 2 * Math.PI, false);
ctx.fillStyle = fill;
ctx.fill();
}
function drawCircleHires(canvas, cx, cy, r, fill){
// get device resolution
let maxWidth = window.screen.availWidth
let maxHeight = window.screen.availHeight
//get scale by maximum device pixel length
let maxDim = Math.max(maxWidth, maxHeight)
scale = maxDim/canvas.width
// adjust canvas size for rendering context
canvas.width *=scale;
canvas.height *=scale;
const ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.arc(cx*scale, cy*scale, r*scale, 0, 2 * Math.PI, false);
ctx.fillStyle = fill;
ctx.fill();
}
function drawCircleAdaptiveRes(canvas, cx, cy, r, fill){
// calculate scaling factor according to rendered bounding box
let {width, height} = canvas.getBoundingClientRect();
scale = width/canvas.width
// adjust canvas size for rendering context
canvas.width *=scale;
canvas.height *=scale;
const ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.arc(cx*scale, cy*scale, r*scale, 0, 2 * Math.PI, false);
ctx.fillStyle = fill;
ctx.fill();
}
canvas,
svg {
display: block;
width:100%;
outline: 1px solid #ccc;
margin-bottom:1em;
}
<h3>SVG</h3>
<svg id="svg" viewBox="0 0 100 50">
<circle id="circle" cx="100" cy="50" r="25" />
</svg>
<h3>Canvas - fixed resolution</h3>
<canvas id="canvas" width="100" height="50"></canvas>
<h3>Canvas - resolution based on rendered size</h3>
<canvas id="canvasAdaptiveRes" width="100" height="50"></canvas>
<h3>Canvas - resolution based on max device resolution</h3>
<canvas id="canvasHires" width="100" height="50"></canvas>
The above snippet illustrates:
- "Canvas - fixed resolution" – your canvas attribute size is not tied to the actual rendering size. Albeit, the rendering becomes blurry
- "Canvas - resolution based on rendered size" calculates a scaling factor based on the current rendering size. This ensures a crisp rendering on load. However you would need to update the rendering on resize to get the best rendering (may also become blurry)
- "Canvas - resolution based on max device resolution" – sets a scaling factor for a hires rendering based on the maximum device resolution. While the rendering won't get blurry on resize it may also be overly crisp (jagged edges). However this problem may be a good compromise as you don't need to add any resize events or observers to update the rendering. Also, the overly crisp edges are not noticeable on hi-pixel-density screens.
发布者:admin,转转请注明出处:http://www.yc00.com/questions/1744306568a4567741.html
innerWidth
,innerHeight
and other browser/DOM APIs. – ggorlen Commented Mar 22 at 19:44