I'm trying to create an interface (HTML canvas/Vanilla JS) where a user can:
Upload an image
Draw on the image
Save the new image in the same resolution as the original from step (1)
I'm stuck on (2). The x and y coordinates are all wrong when I try to draw. How far off they are depends on the size/shape of the image uploaded. I have made a codepen to demonstrate, because the Stack code snippet below was behaving strangely when run.
I am a JS/HTML rookie but my hypothesis is that the incorrect coordinates are based on the image's original resolution (which is retained when I save the resultant image) and not the image's resolution as it is displayed in the canvas.
Any help much appreciated.
const fileInput = document.querySelector(".file-input"),
inputImg = document.querySelector(".input-img img"),
chooseImgBtn = document.querySelector(".choose-img"),
saveImgBtn = document.querySelector(".save-img");
drawOnImage();
fileInput.addEventListener("change", async(e) => {
const [file] = fileInput.files;
// displaying the uploaded image
const image = document.createElement("img");
image.src = await fileToDataUri(file);
// enbaling the brush after after the image
// has been uploaded
image.addEventListener("load", () => {
drawOnImage(image);
});
return false;
});
function fileToDataUri(field) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.addEventListener("load", () => {
resolve(reader.result);
});
reader.readAsDataURL(field);
});
}
const colorElement = document.getElementsByName("colorRadio");
let color;
colorElement.forEach((c) => {
if (c.checked) color = c.value;
});
colorElement.forEach((c) => {
c.onclick = () => {
color = c.value;
};
});
function drawOnImage(image = null) {
const canvasElement = document.getElementById("canvas");
const context = canvasElement.getContext("2d");
// if an image is present,
// the image passed as a parameter is drawn in the canvas
if (image) {
const imageWidth = image.width;
const imageHeight = image.height;
// rescaling the canvas element
canvasElement.width = imageWidth;
canvasElement.height = imageHeight;
context.drawImage(image, 0, 0, imageWidth, imageHeight);
}
let isDrawing;
canvasElement.onmousedown = (e) => {
isDrawing = true;
context.beginPath();
context.lineWidth = size;
context.strokeStyle = "black";
context.lineJoin = "round";
context.lineCap = "round";
context.moveTo(e.clientX, e.clientY);
};
canvasElement.onmousemove = (e) => {
if (isDrawing) {
context.lineTo(e.clientX, e.clientY);
context.stroke();
}
};
canvasElement.onmouseup = function() {
isDrawing = false;
context.closePath();
};
}
chooseImgBtn.addEventListener("click", () => fileInput.click());
/* Import Google font - Poppins */
#import url('https://fonts.googleapis.com/css2?family=Poppins:wght#400;500;600&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
body {
display: flex;
padding: 10px;
min-height: 90vh;
align-items: center;
justify-content: center;
background: #9c9c9c;
}
.container {
width: 1200px;
padding: 30px 35px 35px;
background: #fff;
border-radius: 10px;
box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
}
.container .editor-panel,
.container .controls .reset-filter,
.container .controls .save-img {
opacity: 0.5;
pointer-events: none;
}
.container h2 {
margin-top: -8px;
font-size: 22px;
font-weight: 500;
}
.container .wrapper {
display: flex;
margin: 20px 0;
min-height: 335px;
}
.editor-panel .options,
.controls {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.wrapper .canvas-wrapper {
flex-grow: 1;
display: flex;
overflow: hidden;
margin-left: 20px;
border-radius: 5px;
align-items: center;
justify-content: center;
}
#canvas {
max-width: 700px;
max-height: 650px;
width: 100%;
height: 100%;
border-radius: 5px;
border: 1px solid #ccc;
object-fit: contain;
}
.controls button {
padding: 11px 20px;
font-size: 14px;
border-radius: 3px;
outline: none;
color: #fff;
cursor: pointer;
background: none;
transition: all 0.3s ease;
text-transform: uppercase;
}
.controls .choose-img {
background: #6C757D;
border: 1px solid #6C757D;
}
.controls .save-img {
margin-left: 5px;
background: #5372F0;
border: 1px solid #5372F0;
}
<link rel="stylesheet" href="https://unpkg.com/boxicons#2.1.2/css/boxicons.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.1.1/css/all.min.css" />
<div class="container">
<h2>Drawing</h2>
<div class="wrapper">
<div class="canvas-wrapper">
<canvas id="canvas" style="border: 1px solid black;"></canvas>
</div>
</div>
<div class="controls">
<input type="file" class="file-input" accept="image/*" hidden>
<button class="choose-img">Choose Input Image</button>
<div class="row">
<button class="save-img">Save</button>
</div>
</div>
</div>
You've got two separate problems here. The first is using clientX and clientY to get the mouse position. These are the X and Y coordinates relative to the viewport, such that (0, 0) is the top left corner of the browser window, not the canvas. You want to use offsetX and offsetY instead, which are relative to the element that triggered the event.
The second problem is using CSS to modify the size of the canvas (width: 100%; height: 100%). This changes the physical size of the element on the screen, but not the dimensions of the canvas context, which you set when uploading an image. For example, let's say you set canvas.width and canvas.height (the size of the canvas context) to be 200x100 and you set its CSS width and height to be 400x200. The element will be 400 pixels wide on your screen and if you place your mouse halfway across, the offsetX will be 200. However, drawing anything in the canvas context with an X of 200 will place it at the far right edge, because the context width is 200, not 400.
To deal with this, you either need to not change the canvas's size in CSS at all, or account for the difference in your JS by dividing the mouse's position by the ratio between the element's physical size and the canvas's context size.
Here's an example. The canvas is upscaled by 2x via CSS to be 400x200. The black rectangle is drawn using the exact position of the mouse, while the red rectangle divides down to get the correct corresponding position on the canvas.
const mouse = document.getElementById("mouse");
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
// 200x100 canvas size
canvas.width = 200;
canvas.height = 100;
canvas.addEventListener("mousemove", e => {
mouse.innerText = `mouse x: ${e.offsetX}`;
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw at 1:1 physical size to canvas size
ctx.fillStyle = "#000";
ctx.fillRect(e.offsetX, e.offsetY, 20, 20);
// draw at 2:1 physical size to canvas size
ctx.fillStyle = "#f00";
ctx.fillRect(e.offsetX / 2, e.offsetY / 2, 20, 20);
// or if the ratio is not known:
const width_ratio = canvas.clientWidth / canvas.width;
const height_ratio = canvas.clientHeight / canvas.height;
ctx.fillRect(e.offsetX / width_ratio, e.offsetY / height_ratio, 20, 20);
});
#canvas {
width: 400px;
height: 200px;
border: 1px solid #000;
}
<div id="mouse"></div>
<canvas id="canvas"></canvas>
Related
When I make the HTML canvas bigger, the point I click and the point I draw is very far away. When instead the canvas is of standard size it is perfect. How can I fix it? I've tried to do it in CSS with width of 100vw; but nothing. I also tried to define it in the HTML but in any case also there the stroke is wrong with respect to the position of the mouse.
#import "compass/css3";
html, body {
width: 100%;
height: 100%;
margin: 0px;
overflow: hidden;
&:hover {
span {
display: none;
}
}
}
canvas {
cursor: crosshair;
border: 1px solid red;
width: 100vw;
height: 100vh;
}
span {
font-family: 'Georgia', cursive;
font-size: 40px;
position: fixed;
top: 50%;
left: 50%;
color: #000;
margin-top: -40px;
margin-left: -200px;
}
<canvas id='canvas'></canvas>
const canvas = document.querySelector('canvas');
const ctx = canvas.getContext('2d');
let isDrawing = false;
let lastX = 0;
let lastY = 0;
canvas.addEventListener('mousedown', (e) => {
isDrawing = true;
[lastX, lastY] = [e.clientX, e.clientY];
});
canvas.addEventListener('mousemove', (e) => {
if (!isDrawing) return;
ctx.beginPath();
ctx.moveTo(lastX, lastY);
ctx.lineTo(e.offsetX, e.offsetY);
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
ctx.stroke();
[lastX, lastY] = [e.offsetX, e.offsetY];
});
canvas.addEventListener('mouseup', () => isDrawing = false);
canvas.addEventListener('mouseout', () => isDrawing = false);
The issue is that you are using e.clientX and e.clientY for the mouse position when you start drawing. clientX and clientY are relative to the viewport (browser window), so when you increase the canvas size, the mouse position becomes incorrect. To fix this, you need to use e.offsetX and e.offsetY instead. These values are relative to the canvas, so they will always be accurate, regardless of canvas size. So your code should look like this:
canvas.addEventListener('mousedown', (e) => {
isDrawing = true;
[lastX, lastY] = [e.offsetX, e.offsetY];
});
I'd like two left canvas (layered on top of each other), to fit the left column (100% width minus the right column's 300px width).
It should work even if the canvas' height and width change dynamically, see here after 1 second and 3 seconds, when the canvas width changes from 500 to 1500 pixels wide.
var c1 = document.getElementById("canvas1"), ctx1 = c1.getContext("2d");
var c2 = document.getElementById("canvas2"), ctx2 = c2.getContext("2d");
setTimeout(() => {
ctx1.canvas.width = 500;
ctx1.canvas.height = 300;
ctx1.rect(0, 0, 500, 300);
ctx1.fill();
ctx2.canvas.width = 500;
ctx2.canvas.height = 300;
ctx2.rect(50, 50, 100, 100);
ctx2.fillStyle = "green";
ctx2.fill();
}, 1000);
setTimeout(() => {
ctx1.canvas.width = 1500;
ctx1.canvas.height = 500;
ctx1.rect(0, 0, 1500, 500);
ctx1.fill();
ctx2.canvas.width = 1500;
ctx2.canvas.height = 500;
ctx2.rect(100, 100, 100, 100);
ctx2.fillStyle = "red";
ctx2.fill();
}, 3000);
.container { display: flex; align-items: center; justify-content: center; width: 100%; height: 100%; background-color: yellow; }
.column { border: 1px solid black; }
.canvas-wrapper { margin: 1rem; flex: 1; }
#canvas2 { position: absolute; top: 0; left: 0; }
.right-column { width: 300px; }
<div class="container">
<div class="canvas-wrapper column">
<canvas id="canvas1"></canvas>
<canvas id="canvas2"></canvas>
</div>
<div class="right-column column">
Hello world
</div>
</div>
Here is how it should look like, in both cases it should be rescaled to fit the left column.
In the second case (with the red square), the canvas should be scaled down to fit in the left column (for example to a width of ~1000px if the browser viewport width is 1300 px, minus the 300 px for the right column), even if the real canvas width is still 1500 px.
I have tried multiple variations of flexbox, without success.
If possible, I'd like to keep flex and avoid calc(100% - 300px) rules.
TL;DR: How to make two layered canvas of the same size on top of each other (this size can vary) fit automatically a column in a flex layout?
The trick with FlexBox is to make the left column have a min-width:0, this allows it to shrink, and your 300px right column can then stay the same.
Also you will want to make another div for you canvas-wrapper, so that margins etc work as expected.
Eg.
var c1 = document.getElementById("canvas1"), ctx1 = c1.getContext("2d");
var c2 = document.getElementById("canvas2"), ctx2 = c2.getContext("2d");
setTimeout(() => {
ctx1.canvas.width = 500;
ctx1.canvas.height = 300;
ctx1.rect(0, 0, 500, 300);
ctx1.fill();
ctx2.canvas.width = 500;
ctx2.canvas.height = 300;
ctx2.rect(50, 50, 100, 100);
ctx2.fillStyle = "green";
ctx2.fill();
}, 1000);
setTimeout(() => {
ctx1.canvas.width = 1500;
ctx1.canvas.height = 500;
ctx1.rect(0, 0, 1500, 500);
ctx1.fill();
ctx2.canvas.width = 1500;
ctx2.canvas.height = 500;
ctx2.rect(100, 100, 100, 100);
ctx2.fillStyle = "red";
ctx2.fill();
}, 3000);
.container {
display: flex;
align-items: center;
justify-content: center;
background-color: yellow;
}
.column {
border: 1px solid black;
}
.canvas-wrapper {
margin: 1rem;
flex: 1;
position: relative;
}
#canvas1 {
width: 100%;
}
#canvas2 {
position: absolute;
top: 0;
left: 0;
width: 100%;
}
.left-column {
min-width: 0;
}
.right-column {
width: 300px;
min-width: 300px;
}
<div class="container">
<div class="left-column column">
<div class="canvas-wrapper">
<canvas id="canvas1"></canvas>
<canvas id="canvas2"></canvas>
</div>
</div>
<div class="right-column column">
Hello world
</div>
</div>
Does changing to grid help?
This snippet has two columns in the grid, the first one taking what's left after the 300px column. The width and height of the canvases is set to maximum of the cell dimensions.
var c1 = document.getElementById("canvas1"),
ctx1 = c1.getContext("2d");
var c2 = document.getElementById("canvas2"),
ctx2 = c2.getContext("2d");
setTimeout(() => {
ctx1.canvas.width = 500;
ctx1.canvas.height = 300;
ctx1.rect(0, 0, 500, 300);
ctx1.fill();
ctx2.canvas.width = 500;
ctx2.canvas.height = 300;
ctx2.rect(50, 50, 100, 100);
ctx2.fillStyle = "green";
ctx2.fill();
}, 1000);
setTimeout(() => {
ctx1.canvas.width = 1500;
ctx1.canvas.height = 500;
ctx1.rect(0, 0, 1500, 500);
ctx1.fill();
ctx2.canvas.width = 1500;
ctx2.canvas.height = 500;
ctx2.rect(100, 100, 100, 100);
ctx2.fillStyle = "red";
ctx2.fill();
}, 3000);
.container {
display: grid;
grid-template-columns: 1fr 300px;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
background-color: yellow;
}
.column {
border: 1px solid black;
}
.canvas-wrapper {
margin: 1rem;
flex: 1;
}
#canvas2 {
position: absolute;
top: 0;
left: 0;
}
canvas {
max-width: 100%;
max-height: 100%;
}
.right-column {
width: 300px;
}
<div class="container">
<div class="canvas-wrapper column">
<canvas id="canvas1"></canvas>
<canvas id="canvas2"></canvas>
</div>
<div class="right-column column">
Hello world
</div>
</div>
I'm forking Louis Hoebregts's brilliant Flowing Image on code pen, & attempting to get to modify it for my own art.
UPDATE: as suggested by commentators, I looked at the Chrome Dev console, it complains:
Fetch API cannot load file:///C:/Users/Sam/Downloads/flowing-imagehow-to/flowing-imagehow-to/dist/rowling-dark-bg.jpg. URL scheme "file" is not supported.
I tried removing dashes from the image file name but to no avail.
If I use a web URL that complains
Access to fetch at 'https://pottertour.co.uk/blog/images/rowling/rowling-dark-bg.jpg' from origin 'null' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
but I want a relative file path anyway, I want to load an image locally. Any help pointing me to what I need to do appreciated.
JAVASCRIPT:
let img;
const detail = 6;
let particles = [];
let grid = [];
let particleImage;
let ctx;
function preload() {
img = loadImage('**https://pottertour.co.uk/blog/images/rowling/rowling-dark-bg.jpg**');
}
class Particle {
constructor (x, y) {
this.x = x || random(width);
this.y = y || random(height);
this.prevX = this.x;
this.speed = 0;
this.v = random(0, 0.7);
}
update (speed) {
if (grid.length) {
this.speed = grid[floor(this.y / detail)][floor(this.x / detail)] * 0.97;
}
this.x += (1 - this.speed) * 3 + this.v;
if (this.x > width) {
this.x = 0;
}
}
draw () {
image(particleImage, this.x, this.y);
}
}
/* ====== STEP 1 ====== */
function step1 () {
clear();
noLoop();
image(img, 0, 0, width, height);
noFill();
stroke(120);
strokeWeight(1);
strokeCap(SQUARE);
ctx.globalAlpha = 1;
for (let y = 0; y < height; y+=detail) {
for (let x = 0; x < width; x+=detail) {
rect(x + 0.5, y + 0.5, detail, detail);
}
}
}
...
function setup () {
const canvas = createCanvas(100,100);
ctx = canvas.drawingContext;
pixelDensity(1);
particleImage = createGraphics(8, 8);
particleImage.fill(255);
particleImage.noStroke();
particleImage.circle(4, 4, 4);
windowResized();
document.querySelector('#step').addEventListener('input', () => {
if (window['goToStep' + step.value]) {
window['goToStep' + step.value]();
}
draw();
});
}
function windowResized () {
const imgRatio = img.width/img.height;
if (windowWidth/windowHeight > imgRatio) {
resizeCanvas(floor(windowHeight * imgRatio), floor(windowHeight));
} else {
resizeCanvas(floor(windowWidth), floor(windowWidth / imgRatio));
}
noiseSeed(random(100));
if (window['goToStep' + step.value]) {
window['goToStep' + step.value]();
}
draw();
}
const texts = document.querySelectorAll('section p');
function draw () {
window['step' + step.value]();
texts.forEach(text => text.style.display = 'none');
texts[step.value - 1].style.display = 'block';
}
I tried downloading my fork and running it on my computer, under the assumption maybe Codepen doesn't like externally hosted image files, but it didn't work.
I think the problem is in the Javascript above. Probably in the setup function? Is there something there which is fussy about the dimensions of images that the thing loads? How would I fix that?
I do apologise, my Javascript is knowledge is presently cow headed, I just hack, Javascript is a holiday from App development for me.
HTML:
<input type="range" min="1" max="6" step="1" id="step" value="1">
<section>
<p>Draw an image and divide it into a grid</p>
<p>Get the brightness of every cell</p>
<p>Draw particles moving from left to right</p>
<p>Update each particle's speed based on the brightness of its position</p>
<p>Fade each particle based on its speed</p>
<p>Do not clear your scene on each frame, to let the particles fade out</p>
</section>
CSS
body {
margin: 0;
overflow: hidden;
display: flex;
height: 100vh;
background: black;
}
canvas {
margin: auto;
touch-action: none;
}
#mixin track {
box-sizing: border-box;
height: 6px;
background: #fff;
-webkit-appearance: none;
appearance: none;
}
#mixin thumb {
box-sizing: border-box;
width: 30px;
height: 30px;
border-radius: 50%;
background: #fff;
border: 2px solid black;
-webkit-appearance: none;
appearance: none;
cursor: grab;
}
input {
position: fixed;
left: 50%;
bottom: 20px;
transform: translateX(-50%);
width: 80%;
height: 34px;
max-width: 400px;
background: transparent;
-webkit-appearance: none;
appearance: none;
&:active {
cursor: grabbing;
}
&::-webkit-slider-runnable-track {#include track }
&::-moz-range-track { #include track }
&::-ms-track { #include track }
&::-webkit-slider-thumb {margin-top: -12px;#include thumb}
&::-moz-range-thumb { #include thumb }
&::-ms-thumb {margin-top:0px;#include thumb}
}
section {
box-sizing: border-box;
font-size: 30px;
color: white;
position: fixed;
left: 0;
top: 20px;
width: 100%;
text-align: center;
padding: 10px 10%;
z-index: 10;
pointer-events: none;
text-shadow: 0 0 3px black, 0 0 4px black, 0 0 5px black;
background: rgba(0, 0, 0, 0.7);
p {
margin: 0;
}
#media (max-width: 500px) {
font-size: 24px;
}
}
You could use the developer mode of Chrome to confirm whether there are exceptions, and debug the js.
I want to append a big canvas (width=2000) into dom, and scale it down so it fits the viewport. But then the page size get's unnecessarily big. How can I avoid the page size getting big while having a big canvas scaled down.
function addCanvas(element) {
const canvas = document.createElement('canvas');
canvas.width = 2000;
canvas.height = 2000;
canvas.style.transform = 'scale(0.2)';
element.append(canvas);
}
addCanvas(document.getElementById('app'));
#app {
width: 100%;
height: 100%;
background: #ccc;
}
#app canvas {
transform-origin: 0px 0px;
background: red;
}
<div id="app">
</div>
I am not sure, but I think the Code below is excactly what you wanted, I just added
max-width: 100vw;
max-height: 100vh;
and removed your scaling, and added the conventional resets.
function addCanvas(element) {
const canvas = document.createElement('canvas');
canvas.width = 2000;
canvas.height = 2000;
element.append(canvas);
}
addCanvas(document.getElementById('app'));
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#app {
background: #ccc;
/* Solution for additional Comment */
overflow: hidden;
max-width: 100vw;
max-height: 100vh;
display: flex;
justify-content: center;
}
#app canvas {
background: red;
/* Solution */
max-width: 100vw;
max-height: 100vh;
}
<div id="app">
</div>
Is there a way I can create a canvas inside a dynamic re-sizing flex-box container?
Preferably a CSS only solution but I think JavaScript is required for redraw?
I think one solution could be to listen to the re-sizing event and then scale the canvas to meet the size of the flex box parent then force a redraw but I would preferably like to use as much CSS as possible or a more clean/less code solution.
The current approach is CSS based where the canvas is re-sized according to the parent flex box element. The graphics are blurred, re positioned and overflowed from the canvas in the below screenshot.
CSS:
html,body{
margin:0;
width:100%;
height:100%;
}
body{
display:flex;
flex-direction:column;
}
header{
width:100%;
height:40px;
background-color:red;
}
main{
display:flex;
flex: 1 1 auto;
border: 1px solid blue;
width:80vw;
}
canvas{
flex: 1 1 auto;
background-color:black;
}
HTML:
<header>
</header>
<main>
<canvas id="stage"></canvas>
</main>
Javascript:
$( document ).ready(function() {
var ctx = $("#stage")[0].getContext("2d");
ctx.strokeStyle="#FFFFFF";
ctx.beginPath();
ctx.arc(100,100,50,0,2*Math.PI);
ctx.stroke();
});
JS fiddle showing the issue: https://jsfiddle.net/h2v1w0a1/
Think of canvas as an image. If you scale it to a different size than the original it will appear blurry at some point, and perhaps early on depending on interpolation algorithm chosen by the browser. For canvas the browser tend to chose bi-linear over bi-cubic so there is higher risk of blur than with an actual image.
You will want to have things in canvas rendered as sharp as possible and the only way to this is to adapt the size to the parent using JavaScript, and avoid CSS (the latter is good for things like printing, or when you need "retina resolutions").
To get the parent container size in pixels you can use getComputedStyle() (see update below):
var parent = canvas.parentNode,
styles = getComputedStyle(parent),
w = parseInt(styles.getPropertyValue("width"), 10),
h = parseInt(styles.getPropertyValue("height"), 10);
canvas.width = w;
canvas.height = h;
Fiddle
(Note: Chrome seem to have some issues with computed flex at the moment)
Update
Here's a simpler way:
var rect = canvas.parentNode.getBoundingClientRect();
canvas.width = rect.width;
canvas.height = rect.height;
Fiddle
The only CSS solution I can think about is the use of object-fit:none (related: How does object-fit work with canvas element?)
$(document).ready(function() {
var ctx = $("#stage")[0].getContext("2d");
ctx.strokeStyle = "#FFFFFF";
ctx.beginPath();
ctx.arc(100, 100, 50, 0, 2 * Math.PI);
ctx.stroke();
});
html,
body {
margin: 0;
width: 100%;
height: 100%;
}
body {
display: flex;
flex-direction: column;
}
header {
width: 100%;
height: 40px;
background-color: red;
}
main {
display: flex;
flex: 1 1 auto;
border: 1px solid blue;
width: 80vw;
}
canvas {
flex: 1 1 auto;
background-color: black;
object-fit:none;
object-position:top left;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<header>
</header>
<main>
<canvas id="stage"></canvas>
</main>
Using object-fit provides a pure CSS solution. I think you need to check for yourself which value is better to set to object-fit between cover and contain, there are two examples below.
$(document).ready(function() {
var ctx = $("#stage")[0].getContext("2d");
ctx.strokeStyle = "#FFFFFF";
ctx.beginPath();
ctx.arc(100, 100, 50, 0, 2 * Math.PI);
ctx.stroke();
});
html,
body {
margin: 0;
width: 100%;
height: 100%;
}
body {
display: flex;
flex-direction: column;
}
header {
width: 100%;
height: 40px;
background-color: red;
}
main {
display: flex;
flex: 1 1 auto;
border: 1px solid blue;
width: 80vw;
}
canvas {
object-fit: cover;
background-color: black;
width: 100%;
height: auto;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<header>
</header>
<main>
<canvas id="stage"></canvas>
</main>
$(document).ready(function() {
var ctx = $("#stage")[0].getContext("2d");
ctx.strokeStyle = "#FFFFFF";
ctx.beginPath();
ctx.arc(100, 100, 50, 0, 2 * Math.PI);
ctx.stroke();
});
html,
body {
margin: 0;
width: 100%;
height: 100%;
}
body {
display: flex;
flex-direction: column;
}
header {
width: 100%;
height: 40px;
background-color: red;
}
main {
display: flex;
flex: 1 1 auto;
border: 1px solid blue;
width: 80vw;
}
canvas {
object-fit: contain;
background-color: black;
width: 100%;
height: auto;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<header>
</header>
<main>
<canvas id="stage"></canvas>
</main>
You will also find both examples in JSFiddle https://jsfiddle.net/vfsgpm3q/, https://jsfiddle.net/vfsgpm3q/1/
One other solution is to create a new containing block, and make it follow the formatting context:
const Canvase = styled.canvas`
flex: auto;
position: relative;
left: 0;
top: 0;
width: 100%;
height: 100%;
`;
It is somewhat complimentary/orthogonal to the object-fit: ... suggested by Temani and Peter