How to make a HTML5 canvas fit dynamic parent/flex box container - javascript

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

Related

Is it possible to see through a div to the one below around cursor?

Hi this is a bit of an odd question, I've seen similar effects to whats I'm going for but not exactly the same not sure if what I want to do is possible.
I want to have two divs stacked with the contents of the div below revealed only in a specific area (around the cursor), is there a way to make only part of a div transparent? Or is there any other way to achieve this effect?
Instead of having the element you want to show in the background you can put it in front and only show part of it via a clip-path;
For the coordinates I use CSS variables though you could also overwrite the style directly.
// Get element from the DOM
const container = document.querySelector('.container');
// Apply event listener
container.addEventListener('mousemove', updateCoords, false);
function updateCoords(event) {
// Get X and Y coordinates
const { offsetX, offsetY } = event;
// Update coordinates
container.style.setProperty('--x', offsetX + 'px');
container.style.setProperty('--y', offsetY + 'px');
}
.container {
border: 1px solid #000;
width: 300px;
height: 300px;
}
/* Show child when hovering the container */
.container:hover .child {
display: block;
}
.child {
clip-path: ellipse(30px 30px at var(--x) var(--y));
display: none;
}
<div class="container">
<img class="child" src="//picsum.photos/300" width="300" height="300" />
</div>
You can use requestAnimationFrame to make the circle move more smoothly
// Get element from the DOM
const container = document.querySelector('.container');
// Apply event listener
container.addEventListener('mousemove', updateCoords, false);
function updateCoords(event) {
// Get X and Y coordinates
const { offsetX, offsetY } = event;
// Update coordinates
requestAnimationFrame(() => {
container.style.setProperty('--x', offsetX + 'px');
container.style.setProperty('--y', offsetY + 'px');
});
}
.container {
border: 1px solid #000;
width: 300px;
height: 300px;
}
/* Show child when hovering the container */
.container:hover .child {
display: block;
}
.child {
clip-path: ellipse(30px 30px at var(--x) var(--y));
display: none;
}
<div class="container">
<img class="child" src="//picsum.photos/300" width="300" height="300" />
</div>
Example with text
// Get element from the DOM
const container = document.querySelector('.container');
// Apply event listener
container.addEventListener('mousemove', updateCoords, false);
function updateCoords(event) {
// Get X and Y coordinates
const {offsetX, offsetY} = event;
// Update coordinates
requestAnimationFrame(() => {
container.style.setProperty('--x', offsetX + 'px');
container.style.setProperty('--y', offsetY + 'px');
});
}
.container {
min-height: 100vh;
min-width: 100vh;
overflow: hidden;
}
.container:hover .code {
display: flex;
}
.display,
.code {
position: absolute;
display: flex;
align-items: center;
justify-content: center;
width: 100vw;
height: 100vh;
background-color: rgb(49, 49, 49);
color: rgb(240, 191, 29);
pointer-events: none;
}
.code {
clip-path: ellipse(100px 100px at var(--x) var(--y));
display: none;
background-color: rgb(3, 3, 3);
color: rgb(101, 253, 101);
}
<div class="container">
<div class="display">
<h1>Header</h1>
</div>
<div class="code">
<h3><h1>Header</h1></h3>
</div>
</div>

Change element padding based on element height and parent height with javascript

I need to change the size of h1's top padding based on the height of h1 and it's parent.
Padding top would be (h1's height - h1's parent height) / 2
I still don't understand why the JavaScript code isn't working.
window.onload = function() {
const h1 = document.getElementsByTagName("h1")[0];
h1.innerHTML = document.title;
h1.style.paddingTop = (((h1.parentElement.clientHeight - h1.clientHeight) / 2).toString());
console.log(((h1.parentElement.clientHeight - h1.clientHeight) / 2).toString());
}
header {
border-bottom: solid 1px black;
position: fixed;
height: 10vh;
width: 100%;
}
h1 {
margin: 0px;
margin-top: auto;
}
<header>
<h1>Text</h1>
</header>
Simply because you have not used a unit to identify the value of evaluated padding. Additionally add display: block to the h1 style. Checkout this JSBin demo.
window.onload = function() {
const h1 = document.getElementsByTagName("h1")[0];
h1.innerHTML = document.title;
h1.style.paddingTop = (((h1.parentElement.clientHeight - h1.clientHeight) / 2).toString())+'vh'; // Use unit.
console.log(((h1.parentElement.clientHeight - h1.clientHeight) / 2).toString());
}
header {
border-bottom: solid 1px black;
position: fixed;
height: 10vh;
width: 100%;
}
h1 {
margin: 0px;
margin-top: auto;
display: block;
}
<header>
<h1>Text</h1>
</header>

How to append a canvas with large width scaled down so the page width doesn't get large

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>

How can I prevent Canvas' stroke pixels to stay crisp on zooming in? [duplicate]

This question already has answers here:
HTML5, canvas, and strokeRect: some lines too narrow and blurry
(2 answers)
JavaScript either strokeRect or fillRect blurry depending on translation
(1 answer)
Closed 3 years ago.
How can I prevent the stroke on Canvas to stop from breaking
Zoom into the below image with (mouse scroll)
pixels break on the black rectangle drawn
Edit : adding lineWidth/2 pixels to start coordinates fixes the problem
// grab the DOM SVG element that you want to be draggable/zoomable:
const element = document.getElementById('scene');
// and forward it it to panzoom.
panzoom(element);
const c = document.getElementById("myCanvas");
const ctx = c.getContext("2d");
ctx.strokeRect(20.5, 20.5, 150, 100);
.imageContainer {
overflow: hidden;
border: 3px solid red;
height: 300px;
margin: 0 auto
}
img {
display: table;
margin: 0 auto;
height: 300px;
}
canvas {
display: table;
margin: 0 auto;
height: 300px;
width: 533px;
border: 1px solid gold;
z-index: 2;
position: absolute;
top: 0;
left: 126px;
}
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://unpkg.com/panzoom#8.1.0/dist/panzoom.min.js"></script>
<body>
<div class='imageContainer '>
<div id="scene">
<img id='imageHD' src="https://cdn.wallpapersafari.com/57/46/dY3eN1.jpg">
<canvas id='myCanvas'></canvas>
</div>
</div>
</body>
You could try turning anti-aliasing off for your canvas using
canvas {
image-rendering: optimizeSpeed;
image-rendering: pixelated;
}
The line seems more solid now.
// grab the DOM SVG element that you want to be draggable/zoomable:
const element = document.getElementById('scene');
// and forward it it to panzoom.
panzoom(element);
const c = document.getElementById("myCanvas");
const ctx = c.getContext("2d");
ctx.strokeRect(20, 20, 150, 100);
.imageContainer {
overflow: hidden;
border: 3px solid red;
height: 300px;
margin: 0 auto
}
img {
display: table;
margin: 0 auto;
height: 300px;
}
canvas {
display: table;
margin: 0 auto;
height: 300px;
width: 533px;
border: 1px solid gold;
z-index: 2;
position: absolute;
top: 0;
left: 126px;
}
canvas {
image-rendering: optimizeSpeed;
image-rendering: pixelated;
}
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" rel="stylesheet" />
<script src="https://unpkg.com/panzoom#8.1.0/dist/panzoom.min.js"></script>
<body>
<div class='imageContainer '>
<div id="scene">
<img id='imageHD' src="https://cdn.wallpapersafari.com/57/46/dY3eN1.jpg">
<canvas id='myCanvas'></canvas>
</div>
</div>
</body>

3 Divs side by side overflow one another

I've been chasing around this problem for hours, here's the code:
<!DOCTYPE html>
<html>
<head>
<style>
html, body{
height: 100%;
max-height: 100%;
overflow:hidden;
}
.controls{
display: table;
height: 10%;
margin-top: 1%;
width: 100%;
}
#w1 {
width:25%;
}
#can
float: left;
padding: 0;
margin: 0;
top: 0;
}
#canTwo{
float: left;
padding: 0;
margin: 0;
top: 0;
}
textarea {
outline: none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
font-size: 1.25vw;
height: 100%;
width: 100%;
}
#w2{
width:50%;
}
#w3{
width:25%;
}
.controlbuttons {
display: table-cell;
height: 100%;
}
</style>
</head>
<body>
<div class="controls">
<div class="controlbuttons" id="w1"><canvas id = "can" width = "0" height = "0"></div>
<div class="controlbuttons" id="w2"><textarea rows="3" cols="50"></textarea></div>
<div class="controlbuttons" id="w3"><canvas id = "canTwo" width = "0" height = "0"></div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function(event) {
fitToContainer();
});
var canvas = document.getElementById("can"),
ctx = canvas.getContext('2d'),
canvasTwo = document.getElementById("canTwo"),
ctxTwo = canvasTwo.getContext('2d');
function fitToContainer(){
var control = document.getElementsByClassName("controlbuttons")[0];
var h = control.clientHeight;
var w = control.clientWidth;
canvas.height = h;
canvas.width = w;
canvasTwo.height = h;
canvasTwo.width = w;
ctx.fillStyle = "green";
ctx.fillRect(0, 0, 5000, 5000);
ctxTwo.fillStyle = "green";
ctxTwo.fillRect(0, 0, 5000, 5000);
}
</script>
</body>
</html>
jsfiddle:
https://jsfiddle.net/ca3uw837/
Basically I have one textarea and it's width takes 50% of the page and it is exactly in the middle, there are two
canvases to it's side which take 25% width.
I am trying to get them to align perfectly(same height, exactly one next to the other) but here is how it looks on my pc:
What am I supposed to do? use flexbox? I am not sure I know how to achieve it as canvases are very tricky with their sizing technique. Thank you so much for your time.
Apply flexbox to .controls to align the child elements. Also apply box-sizing: border-box to textbox as the default padding adds with the 100% height of the textbox. border-box will make the padding inclusive of height.
document.addEventListener("DOMContentLoaded", function(event) {
fitToContainer();
});
var canvas = document.getElementById("can"),
ctx = canvas.getContext('2d'),
canvasTwo = document.getElementById("canTwo"),
ctxTwo = canvasTwo.getContext('2d');
function fitToContainer() {
var control = document.getElementsByClassName("controlbuttons")[0];
var h = control.clientHeight;
var w = control.clientWidth;
canvas.height = h;
canvas.width = w;
canvasTwo.height = h;
canvasTwo.width = w;
ctx.fillStyle = "green";
ctx.fillRect(0, 0, 5000, 5000);
ctxTwo.fillStyle = "green";
ctxTwo.fillRect(0, 0, 5000, 5000);
}
html,
body {
height: 100%;
max-height: 100%;
overflow: hidden;
}
.controls {
display: flex;
height: 10%;
margin-top: 1%;
width: 100%;
}
#w1 {
width: 25%;
}
#can float: left;
padding: 0;
margin: 0;
top: 0;
}
#canTwo {
float: left;
padding: 0;
margin: 0;
top: 0;
}
textarea {
outline: none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
font-size: 1.25vw;
height: 100%;
width: 100%;
box-sizing: border-box;
}
#w2 {
width: 50%;
}
#w3 {
width: 25%;
}
.controlbuttons {
height: 100%;
}
<div class="controls">
<div class="controlbuttons" id="w1"><canvas id="can" width="0" height="0"></div>
<div class="controlbuttons" id="w2"><textarea rows="3" cols="50"></textarea></div>
<div class="controlbuttons" id="w3"><canvas id = "canTwo" width = "0" height = "0"></div>
</div>
To align table-cell items to the top you should use: vertical-align: top. Both canvas are missing height and width property, set them to 100%. Add box-sizing: border-box to the textarea:
box-sizing: border-box
The width and height properties (and min/max properties) includes
content, padding and border, but not the margin
So:
textarea {
/** ... rest of styles ...*/
margin: 0;
box-sizing: border-box;
float: left;
}
.controlbuttons { vertical-align: top; } /* Align items to the top */
.controlbuttons canvas { height: 100%; width: 100%; }
Demo: (Tested in firefox 53.0.2 & Chrome 56.0.2924.87)
document.addEventListener("DOMContentLoaded", function(event) {
fitToContainer();
});
var canvas = document.getElementById("can"),
ctx = canvas.getContext('2d'),
canvasTwo = document.getElementById("canTwo"),
ctxTwo = canvasTwo.getContext('2d');
function fitToContainer() {
var control = document.getElementsByClassName("controlbuttons")[0];
var h = control.clientHeight;
var w = control.clientWidth;
canvas.height = h;
canvas.width = w;
canvasTwo.height = h;
canvasTwo.width = w;
ctx.fillStyle = "green";
ctx.fillRect(0, 0, 5000, 5000);
ctxTwo.fillStyle = "green";
ctxTwo.fillRect(0, 0, 5000, 5000);
}
html,
body {
height: 100%;
max-height: 100%;
overflow: hidden;
}
.controls {
display: table;
height: 10%;
margin-top: 1%;
width: 100%;
}
#w1 {
width: 25%;
}
textarea {
outline: none;
-webkit-box-shadow: none;
-moz-box-shadow: none;
box-shadow: none;
font-size: 1.25vw;
height: 100%;
width: 100%;
margin: 0;
box-sizing: border-box;
float: left;
}
#w2 {
width: 50%;
}
#w3 {
width: 25%;
}
.controlbuttons {
display: table-cell;
height: 100%;
vertical-align: top;
}
.controlbuttons canvas {
float: left;
padding: 0;
margin: 0;
top: 0;
height: 100%;
width: 100%;
}
<!DOCTYPE html>
<html>
<body>
<div class="controls">
<div class="controlbuttons" id="w1">
<canvas id="can" width="0" height="0"></canvas>
</div>
<div class="controlbuttons" id="w2">
<textarea rows="3" cols="50"></textarea>
</div>
<div class="controlbuttons" id="w3">
<canvas id = "canTwo" width = "0" height = "0"></canvas>
</div>
</div>
</body>
</html>

Categories

Resources