I found this pretty awesome blog post that explains how to resize a canvas to fit any screen http://www.html5rocks.com/en/tutorials/casestudies/gopherwoord-studios-resizing-html5-games/ I implemented it and it is sizing it flawlessly but I get this problem that my objects appear off screen when the canvas is too little.
For example for my game I define a world with dimensions width 480 and height 1024, if I put an element on x axis at about 400 but the display that is currently used is 360px wide then the canvas would have correct ratio of width to height 0.46875 but the object will not be visible.
To solve this problem I suppose I need to define a ratio between the absolute world dimensions and the screen dimensions and multiply by that when I'm calculating the object's position, however my question is - is there a way for the canvas to do that automatically?
Both the canvas itself and the canvas element have a width and height. If they're not the same, the content of the canvas is automatically scaled to fit the element. So yes, you could define your canvas to be the size you want, and define the element such that it scales. Naturally, though, everything scales, and if the aspect ratio is different, your shapes get distorted.
The width and height of the canvas within the canvas element are controlled by the width and height attributes of the canvas element. (They default to 300 and 150, respectively.)
The width and height of the canvas element are controlled by CSS; they default to the width and height of the canvas, but you can override that with CSS.
For example: Here's a canvas that's 300x300 (thanks to the CSS), within a canvas element that's only 300x150 (because those are the default width and height of the canvas element). You can see the effect of scaling when I draw a circle in it — it comes out oval instead:
// Get the canvas element
const canvas = document.getElementById("clock");
// Get the 2D rendering context for it
const context = canvas.getContext("2d");
// Show the canvas's dimensions (NOT the dimensions of the element)
document.getElementById("dimensions").innerHTML = canvas.width + "x" + canvas.height;
// Draw a circle -- it comes out oval because of scaling
const path = new Path2D();
path.arc(75, 75, 50, 0, Math.PI * 2, true);
context.fillStyle = context.strokeStyle = "blue";
context.fill(path);
canvas#clock {
width: 300px;
height: 300px;
background: #1E1E1E;
}
<canvas id="clock"></canvas>
<div id="dimensions"></div>
Here's an example where the canvas and its container are the same width and height, so there's no scaling; the code is identical, it's just I've added width="300" height="300" to the canvas HTML:
// Get the canvas element
const canvas = document.getElementById("clock");
// Get the 2D rendering context for it
const context = canvas.getContext("2d");
// Show the canvas's dimensions (NOT the dimensions of the element)
document.getElementById("dimensions").innerHTML = canvas.width + "x" + canvas.height;
// Draw a circle
const path = new Path2D();
path.arc(75, 75, 50, 0, Math.PI * 2, true);
context.fillStyle = context.strokeStyle = "blue";
context.fill(path);
canvas#clock {
/* We could comment out the width and height properties
since they'll default to the values from the attributes
*/
width: 300px;
height: 300px;
background: #1E1E1E;
}
<canvas id="clock" width="300" height="300"></canvas>
<div id="dimensions"></div>
The automatic scaling is handy provided you ensure the aspect ratio of the element and its canvas are the same. If you don't directly set the size of the canvas element, on most browsers it will default to the aspect ratio of the canvas thanks to the width and height attributes. From MDN's coverage of aspect-ratio:
Browsers have added an internal aspect-ratio property that applies to replaced elements and other related elements that accept width and height attributes. This appears in the browser's internal UA stylesheet.
In Firefox, the internal stylesheet rule looks like this:
img, input[type="image"], video, embed, iframe, marquee, object, table {
aspect-ratio: attr(width) / attr(height);
}
Related
I am trying to build a game using HTML5 canvas.
The canvas takes the full width and height of the browser.
canvas.height = innerHeight;
canvas.width = innerWidth;
How can I make the elements inside the canvas, like player, obstacles etc. responsive?
I know doing something like
addEventListener("resize", () => {
canvas.width = innerWidth;
canvas.height = innerHeight - 100;
});
will make the canvas responsive. But what about the elements drawn to the canvas?
One approach is to use two canvases:
The onscreen canvas, which you make responsive by setting its width and height to the viewport width and height (as you do in your resize event listener)
A second canvas element which you create using Javascript. This will have width and height dimensions that will never change (say 600px by 400px), which means you can render your game elements onto it with confidence.
Render the game on this second canvas and then, at the end of each RequestAnimationFrame cycle, copy it onto the first canvas using the ctx.drawImage() function. At this point you can do calculations to get the whole of your second canvas to fit into the first canvas leaving gaps either at the top/bottom, or on the left/right edges.
As a bonus, this will also allow your users to zoom/pan across your second canvas - if your game calls for that sort of thing.
Method 1
This is if you’d like the pixels to scale as well (like with pixel art)
If you want the drawings to scale as well, then change the canvas’s css height and width values, not the width and height attributes. Like this:
canvas.style.height = innerHeight + ‘px’;
canvas.style.width = innerWidth + ‘px’;
This will scale all the drawings as well.
However, using this alone, all drawings will be scaled using interpolation, giving a fuzzy look. To prevent that, add this:
canvas {
image-rendering: optimizeSpeed;
image-rendering: -moz-crisp-edges;
image-rendering: -webkit-optimize-contrast;
image-rendering: -o-crisp-edges;
image-rendering: pixelated;
-ms-interpolation-mode: nearest-neighbor;
}
This will make the browser scale it using nearest-neighbor instead.
Method 2
This method will scale all of the drawings, but will not scale pixels, and will not make it look too pixely nor fuzzy.
Like you originally did, set the width and height attributes of the canvas:
canvas.height = innerHeight;
canvas.width = innerWidth;
But create variables representing how much the canvas was scaled:
var scaleW = newWidth / oldWidth;
var scaleH = newHeight / oldHeight;
Then, add these to your drawing so you don’t have to manually scale every value:
ctx.save(); // saves the ctx
ctx.scale(scaleW, scaleH); // scales the drawings
// all your drawing ...
ctx.restore(); // restores the ctx, removing the scale
Sorry if the first didn’t work, hopefully the second should.
I have 2 canvases, one uses HTML attributes width and height to size it, the other uses CSS:
<canvas id="compteur1" width="300" height="300" onmousedown="compteurClick(this.id);"></canvas>
<canvas id="compteur2" style="width: 300px; height: 300px;" onmousedown="compteurClick(this.id);"></canvas>
Compteur1 displays like it should, but not compteur2. The content is drawn using JavaScript on a 300x300 canvas.
Why is there a display difference?
It seems that the width and height attributes determine the width or height of the canvas’s coordinate system, whereas the CSS properties just determine the size of the box in which it will be shown.
This is explained in the HTML specification:
The canvas element has two attributes to control the size of the element’s bitmap: width and height. These attributes, when specified, must have values that are valid non-negative integers. The rules for parsing non-negative integers must be used to obtain their numeric values. If an attribute is missing, or if parsing its value returns an error, then the default value must be used instead. The width attribute defaults to 300, and the height attribute defaults to 150.
To set the width and height on a canvas, you may use:
canvasObject.setAttribute('width', '150');
canvasObject.setAttribute('height', '300');
For <canvas> elements, the CSS rules for width and height set the actual size of the canvas element that will be drawn to the page. On the other hand, the HTML attributes of width and height set the size of the coordinate system or 'grid' that the canvas API will use.
For example, consider this (jsfiddle):
var ctx = document.getElementById('canvas1').getContext('2d');
ctx.fillStyle = "red";
ctx.fillRect(10, 10, 30, 30);
var ctx2 = document.getElementById('canvas2').getContext('2d');
ctx2.fillStyle = "red";
ctx2.fillRect(10, 10, 30, 30);
canvas {
border: 1px solid black;
}
<canvas id="canvas1" style="width: 50px; height: 100px;" height="50" width="100"></canvas>
<canvas id="canvas2" style="width: 100px; height: 100px;" height="50" width="100"></canvas>
Both have had the same thing drawn on them relative to the internal coordinates of the canvas element. But in the second canvas, the red rectangle will be twice as wide because the canvas as a whole is being stretched across a bigger area by the CSS rules.
Note: If the CSS rules for width and/or height aren't specified then the browser will use the HTML attributes to size the element such that 1 unit of these values equals 1px on the page. If these attributes aren't specified then they will default to a width of 300 and a height of 150.
The canvas will be stretched if you set the width and height in your CSS. If you want to dynamically manipulate the dimension of the canvas you have to use JavaScript like so:
canvas = document.getElementById('canv');
canvas.setAttribute('width', '438');
canvas.setAttribute('height', '462');
The browser uses the css width and height, but the canvas element scales based on the canvas width and height. In javascript, read the css width and height and set the canvas width and height to that.
var myCanvas = $('#TheMainCanvas');
myCanvas[0].width = myCanvas.width();
myCanvas[0].height = myCanvas.height();
Shannimal correction
var el = $('#mycanvas');
el.attr('width', parseInt(el.css('width')))
el.attr('height', parseInt(el.css('height')))
Canvas renders image by buffer, so when you specify the width and height HTML attributes the buffer size and length changes, but when you use CSS, the buffer's size is unchanged. Making the image stretched.
Using HTML sizing.
Size of canvas is changed -> buffer size is changed -> rendered
Using CSS sizing
Size of canvas is changed -> rendered
Since the buffer length is kept unchanged, when the context renders the image,
the image is displayed in resized canvas (but rendered in unchanged buffer).
CSS sets the width and height of the canvas element so it affects the coordinate space leaving everything drawn skewed
Here's my way on how to set the width and height with Vanilla JavaScript
canvas.width = numberForWidth
canvas.height = numberForHeight
I believe CSS has much better machinery for specifying the size of the canvas and CSS must decide styling, not JavaScript or HTML. Having said that, setting width and height in HTML is important for working around the issue with canvas.
CSS has !important rule that allows to override other styling rules for the property, including those in HTML. Usually, its usage is frowned upon but here the use is a legitimate hack.
In Rust module for WebAssembly you can do the following:
fn update_buffer(canvas: &HtmlCanvasElement) {
canvas.set_width(canvas.client_width() as u32);
canvas.set_height(canvas.client_height() as u32);
}
//..
#[wasm_bindgen(start)]
pub fn start() -> Result<(), JsValue> {
// ...
let canvas: Rc<_> = document
.query_selector("canvas")
.unwrap()
.unwrap()
.dyn_into::<HtmlCanvasElement>()
.unwrap()
.into();
update_buffer(&canvas);
// ...
// create resizing handler for window
{
let on_resize = Closure::<dyn FnMut(_)>::new(move |_event: Event| {
let canvas = canvas.clone();
// ...
update_buffer(&canvas);
// ...
window.add_event_listener_with_callback("resize", on_resize.as_ref().unchecked_ref())?;
on_resize.forget();
}
}
There we update the canvas buffer once the WASM module is loaded and then whenever the window is resized. We do it by manually specifying width and height of canvas as values of clientWidth and clientHeight. Maybe there are better ways to update the buffer but I believe this solution is better than those suggested by #SamB, #CoderNaveed, #Anthony Gedeon, #Bluerain, #Ben Jackson, #Manolo, #XaviGuardia, #Russel Harkins, and #fermar because
The element is styled by CSS, not HTML.
Unlike elem.style.width & elem.style.height trick used by #Manolo or its JQuery equivalent used by #XaviGuardia, it will work for canvas whose size is specified by usage as flex or grid item.
Unlike the solution by #Russel Harkings, this also handles resizing. Though I like his answer because it is really clean and easy.
WASM is the future! Haha :D
P.S. there's a ton of .unwrap() because Rust explicitly handles possible failures.
P.P.S.
{
let on_resize = Closure::<dyn FnMut(_)>::new(move |_event: Event| {
let canvas = canvas.clone();
// ...
update_buffer(&canvas);
// ...
window.add_event_listener_with_callback("resize", on_resize.as_ref().unchecked_ref())?;
on_resize.forget();
}
can be done much cleaner with better libraries. E.g.
add_resize_handler(&window, move |e: ResizeEvent| {
let canvas = canvas.clone();
// ...
update_buffer(&canvas);
})
If you want a dynamic behaviour based on, e.g. CSS media queries, don't use canvas width and height attributes. Use CSS rules and then, before getting the canvas rendering context, assign to width and height attributes the CSS width and height styles:
var elem = document.getElementById("mycanvas");
elem.width = elem.style.width;
elem.height = elem.style.height;
var ctx1 = elem.getContext("2d");
...
This question already has answers here:
Size of HTML5 Canvas via CSS versus element attributes
(3 answers)
Closed 3 years ago.
im currently working on a HTML/Javascript Project where i am using a HTML Canvas and the Context2D for drawing.
More or less i'm drawing a part of a 2d world with no fixed tile size.
let width = canvas.width;
let height = canvas.height;
let cellHeight = height/rows * viewSizeMultiplier.y,cellWidth = width/columns * viewSizeMultiplier.x;
The viewSizeMultiplier is like 1/8 for 8 Tiles on the Map. I've struggeld alot by getting a specific Tile when clicking on the Canvas because the canvas.size does not adjust itself by resizing the window.
.canvas {
width: 60%;
height: 80%;
left:5%;
top:10%;
}
That's the way i implemented my canvas in css. For getting the current Tile on my screen i had to calculate the aspect ratio of the diffrent sizes like that:
function getMousePos(canvas, evt) {
var rect = canvas.getBoundingClientRect(),
scaleX = canvas.width / rect.width,
scaleY = canvas.height / rect.height;
return {
x: (evt.clientX - rect.left) * scaleX,
y: (evt.clientY - rect.top) * scaleY
}
}
So my question is why are there 2 diffrent Sizes of the Canvas? If it uses the canvas.size Size does it adjusts its resolution?
Added Snippet :
let canvas = document.getElementsByClassName('canvas')[0];
const canvasWidth= canvas.width;
const actualWidth =canvas.getBoundingClientRect().width;
console.log(canvasWidth,actualWidth);//300 , 522
The CSS styling won't change the pixel dimensions of the canvas after it's first been created. You need to specifically set canvas.width and canvas.height to new dimensions to change the pixel size of the canvas, otherwise you'll end up with nothing more than the original width * height pixels scaled to different sizes.
You'll need to listen for resize events to know when to change the dimensions of the canvas.
Well, the canvas' inner dimensions can be different from the canvas DOM element's dimensions.
canvas.width gives you the canvas inner width, while rect.width only gives you the canvas DOM element's outer width, excluding any portion of the canvas that is drawn beyond the outer width, and that needs scrolling to reach. The same applies for height.
So, in short, whenever you need to scroll to see all your content in a canvas, this implies that your canvas' inner dimensions is larger than its outer dimensions.
I have a canvas defined as
<canvas id="field"></canvas>
style.css:
canvas#field {
width: 500px;
height: 250px;
border: 3px solid black; /* for now */
}
Whenever I try drawing stuff on it, like
var ctx = field.getContext("2d")
// circle
ctx.beginPath();
ctx.arc(100, 75, 50, 0, 2 * Math.PI);
ctx.stroke();
It always ends up being sized relatively to the canvas dimensions. If I make the canvas twice as big, the circle is also twice as big, even though my radius is always 50px. If I make my canvas a square, the circle becomes elongated downwards, and vice versa if I stretch the canvas out sideways.
How do I make the canvas treat the numbers I give it as absolute values?
You should set canvas width and height as HTML attributes instead of CSS rules:
<canvas id="field" width="500" height="250"></canvas>
Quoting MDN:
The <canvas> element has only two attributes, width and height. These are both optional and can also be set using DOM properties. When no width and height attributes are specified, the canvas will initially be 300 pixels wide and 150 pixels high. The element can be sized arbitrarily by CSS, but during rendering the image is scaled to fit its layout size: if the CSS sizing doesn't respect the ratio of the initial canvas, it will appear distorted.
You can also set width and height dynamically using JavaScript:
const canvas = document.querySelector('canvas');
canvas.width = '500';
canvas.height = '250';
The HTML5 <canvas> element does not accept relative sizes (percent) for its width and height properties.
What I'm trying to accomplish is to have my canvas sized relative to the window. This is what I've come up with so far, but I'm wondering if there is a better way that is:
Simpler
Does not require wrapping the <canvas> in a <div>.
Not dependent on jQuery (I use it to get the width/height of the parent div)
Ideally, doesn't redraw on browser resize (but I think that might be a requirement)
See below for the code, which draws a circle in the middle of the screen, 40% wide up to a maximum of 400px.
Live demo: http://jsbin.com/elosil/2
Code:
<!DOCTYPE html>
<html>
<head>
<title>Canvas of relative width</title>
<style>
body { margin: 0; padding: 0; background-color: #ccc; }
#relative { width: 40%; margin: 100px auto; height: 400px; border: solid 4px #999; background-color: White; }
</style>
<script language="javascript" type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.2/jquery.min.js"></script>
<script>
function draw() {
// draw a circle in the center of the canvas
var canvas = document.getElementById('canvas');
var relative = document.getElementById('relative');
canvas.width = $(relative).width();
canvas.height = $(relative).height();
var w = canvas.width;
var h = canvas.height;
var size = (w > h) ? h : w; // set the radius of the circle to be the lesser of the width or height;
var ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.arc(w / 2, h / 2, size/2, 0, Math.PI * 2, false);
ctx.closePath();
ctx.fill();
}
$(function () {
$(window).resize(draw);
});
</script>
</head>
<body onload="draw()">
<div id="relative">
<canvas id="canvas"></canvas>
</div>
</body>
</html>
The canvas width and height attributes are separate from the same canvas's width and height styles. The width and height attributes are the size of the canvas's rendering surface, in pixels, whereas its styles choose a location in the document where the browser should draw the content from the rendering surface. It just so happens that the default value for the width and height styles, if they're not specified, is the rendering surface's width and height. So you're right about #1: there's no reason to wrap it in a div. You can set percentage values for all of the styles on your canvas element, just like any other element.
For #3, it's pretty easy (and cross-browser) to get the size of things with clientWidth and clientHeight, as long as you're not using padding on your canvas element.
I coded up the slightly simplified version here.
For #4, you're right about being out of luck. It's possible to check before setting width and height and leave the canvas alone if it doesn't need resizing, which would eliminate some of the redraws, but you can't get rid of all of them.
EDIT: Portman pointed out I messed up the centering style. Updated version here.
Like said by sethobrien a canvas element has TWO pairs width/height of attributes.
canvas.width / canvas.height are about the size in pixel of the buffer that will contains the result of drawing commands.
canvas.style.width / canvas.style.height are about the size used to show the canvas object in the browser window and they can be in any of the units supported by css.
You can indeed set canvas.width and canvas.height just once, do the drawing in the canvas, setting the style size parameters in percentage and then forget about redrawing the canvas content. Of course this means that the browser will just do the scaling itself like for a regular image loaded from the network so the visible result will show pixel scaling artifacts.
You need to redraw the canvas content after the resize of the canvas element only if you want pixel-perfect results.
Alright. Here is the technique that i ve used to implement the same.
Suppose you have the canvas height=400, for the window's height=480, and you want to change the height of it relatively if the window's height changes to 640.
canvas = document.getElementById("canvas");
canvas.height=window.innerHeight*400/480;
p.s: do not initialize the height of the canvas inside the html tag.
Make use of 'window.innerHeight' (which returns the height of the browser's window.. similarly 'window.innerWidth') any where you want to calculate the relative positions on the window.
Hope you got what you needed.