Scale canvas proportionally inside flexbox - css

Suppose I have the following HTML:
<div>
<canvas class="colorPicker" width="800" height="800"></canvas>
<canvas class="colorPicker" width="800" height="800"></canvas>
</div>
And, this CSS:
div {
display: flex;
width: 100%;
overflow: hidden;
}
div > canvas{
flex-grow: 1;
flex-shrink: 1;
width: 50%;
}
The canvases are not scaled proportionally. They are taller than they are wide. This is visible when I paint circles on the canvas.
(JSFiddle: https://jsfiddle.net/08rckfek/)
Why is this happening?
If I don't use flexbox in the parent div, the canvases are scaled correctly.
Furthermore, it seems weird that I have to specify width: 50% at all. flex-shrink is ignored, even if I specify a basis.
How can I make a row of canvases that automatically scale proportionally based on the width of the parent flexbox?

You need to set align-items as by default its value is stretch
document.addEventListener('DOMContentLoaded', (e) => {
document.querySelectorAll('canvas.colorPicker').forEach((canvas) => {
const ctx = canvas.getContext('2d');
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
const radius = Math.min(canvas.width, canvas.height) / 2;
// Base white circle, so middle has a full color
ctx.beginPath();
ctx.arc(centerX, centerY, radius/2, 0, 2*Math.PI, false);
ctx.closePath();
ctx.fillStyle = '#fff';
ctx.fill();
for (let angle=0; angle<=360; angle+=1) {
const startAngle = (angle-2)*Math.PI/180;
const endAngle = angle * Math.PI/180;
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.arc(centerX, centerY, radius, startAngle, endAngle);
ctx.closePath();
const gradient = ctx.createRadialGradient(centerX, centerY, 0, centerX, centerY, radius);
gradient.addColorStop(0, 'hsl(' + angle + ', 100%, 100%)');
gradient.addColorStop(0.5, 'hsl(' + angle + ', 100%, 50%)');
gradient.addColorStop(1, 'hsl(' + angle + ', 100%, 0%)');
ctx.fillStyle = gradient;
ctx.fill();
}
});
});
div {
display: flex;
width: 100%;
overflow: hidden;
align-items:flex-start;
}
div > canvas{
flex-grow: 1;
flex-shrink: 1;
width: 50%;
}
<div>
<canvas class="colorPicker" width="800" height="800"></canvas>
<canvas class="colorPicker" width="800" height="800"></canvas>
</div>

Related

How to use CSS transform: scale(); to fit child div to parent div responsively in react?

I am really stuck. My div contains fixed size divs, texts and images with a lot of pixel based positioning. How can I just wrap them in another div and make them scale to fit the wrapper?
This might be very similar: https://css-tricks.com/scaled-proportional-blocks-with-css-and-javascript/
Let's say that some elements have a class of .scaled-wrapper, and such elements have a single child named .scaled-content. Then we have a function applyScaling which takes a single .scaled-wrapper element, and scales its child accordingly:
let scaledWrapper = document.getElementsByClassName('scaled-wrapper')[0];
let applyScaling = scaledWrapper => {
// Get the scaled content, and reset its scaling for an instant
let scaledContent = scaledWrapper.getElementsByClassName('scaled-content')[0];
scaledContent.style.transform = 'scale(1, 1)';
let { width: cw, height: ch } = scaledContent.getBoundingClientRect();
let { width: ww, height: wh } = scaledWrapper.getBoundingClientRect();
let scaleAmtX = ww / cw;
let scaleAmtY = wh / ch;
scaledContent.style.transform = `scale(${scaleAmtX}, ${scaleAmtY})`;
};
applyScaling(scaledWrapper);
// ---- The rest of the code is just for the demonstration ui ----
let change = () => {
let w = parseInt(wInp.value);
let h = parseInt(hInp.value);
if (!isNaN(w)) scaledWrapper.style.width = `${w}px`;
if (!isNaN(h)) scaledWrapper.style.height = `${h}px`;
scaledWrapper.getElementsByClassName('scaled-content')[0].innerHTML = textInp.value;
applyScaling(scaledWrapper);
};
let wInp = document.createElement('input');
wInp.setAttribute('placeholder', 'input parent width in px');
wInp.addEventListener('input', change);
wInp.value = '200';
document.body.appendChild(wInp);
let hInp = document.createElement('input');
hInp.setAttribute('placeholder', 'input parent height in px');
hInp.addEventListener('input', change);
hInp.value = '200';
document.body.appendChild(hInp);
let textInp = document.createElement('input');
textInp.setAttribute('placeholder', 'input text content');
textInp.addEventListener('input', change);
textInp.value = 'abc';
document.body.appendChild(textInp);
.scaled-wrapper {
position: absolute;
left: 10px; top: 30px;
width: 200px; height: 200px;
outline: 1px solid red;
z-index: 1;
}
.scaled-content {
box-sizing: border-box;
display: inline-block;
transform-origin: 0 0;
background-color: #ffd0d0;
z-index: -1;
}
<div class="scaled-wrapper"><div class="scaled-content">abc</div></div>
The main idea is to take the ratio of the size between parent/child, and use that to scale the child accordingly. Note that it's important that the child's transform-origin is the top-left corner (having transform-origin: 50% 50% causes the child to be centered around the parent's top-left corner).
Also note that applyScaling needs to be called if the size of the parent ever changes (for example, if the parent is a percentage size of the window, and the window resizes).
Using #Gershom's snippet, but keeping aspect ratio
let scaledWrapper = document.getElementsByClassName('scaled-wrapper')[0];
let applyScaling = scaledWrapper => {
// Get the scaled content, and reset its scaling for an instant
let scaledContent = scaledWrapper.getElementsByClassName('scaled-content')[0];
scaledContent.style.transform = 'scale(1, 1)';
let { width: cw, height: ch } = scaledContent.getBoundingClientRect();
let { width: ww, height: wh } = scaledWrapper.getBoundingClientRect();
// let scaleAmtX = ww / cw;
// let scaleAmtY = wh / ch;
let scaleAmtX = Math.min(ww / cw, wh / ch);
let scaleAmtY = scaleAmtX;
scaledContent.style.transform = `scale(${scaleAmtX}, ${scaleAmtY})`;
};
applyScaling(scaledWrapper);
// ---- The rest of the code is just for the demonstration ui ----
let change = () => {
let w = parseInt(wInp.value);
let h = parseInt(hInp.value);
if (!isNaN(w)) scaledWrapper.style.width = `${w}px`;
if (!isNaN(h)) scaledWrapper.style.height = `${h}px`;
scaledWrapper.getElementsByClassName('scaled-content')[0].innerHTML = textInp.value;
applyScaling(scaledWrapper);
};
let wInp = document.createElement('input');
wInp.setAttribute('placeholder', 'input parent width in px');
wInp.addEventListener('input', change);
wInp.value = '100';
document.body.appendChild(wInp);
let hInp = document.createElement('input');
hInp.setAttribute('placeholder', 'input parent height in px');
hInp.addEventListener('input', change);
hInp.value = '100';
document.body.appendChild(hInp);
let textInp = document.createElement('input');
textInp.setAttribute('placeholder', 'input text content');
textInp.addEventListener('input', change);
textInp.value = 'abc';
document.body.appendChild(textInp);
.wrapper-wrapper {
box-sizing: border-box;
border: solid 2px blue;
position: relative;
}
.scaled-wrapper {
position: relative;
width: 100px; height: 100px;
outline: 1px solid red;
z-index: 1;
}
.scaled-content {
box-sizing: border-box;
display: inline-block;
transform-origin: 0 0;
background-color: #ffd0d0;
z-index: -1;
}
<div class="wrapper-wrapper">
<div class="scaled-wrapper">
<div class="scaled-content">abc</div>
</div>
</div>
For React you can do it like this:
const applyScaling = (scaledWrapper, scaledContent) => {
scaledContent.style.transform = 'scale(1, 1)';
let { width: cw, height: ch } = scaledContent.getBoundingClientRect();
let { width: ww, height: wh } = scaledWrapper.getBoundingClientRect();
let scaleAmtX = Math.min(ww / cw, wh / ch);
let scaleAmtY = scaleAmtX;
scaledContent.style.transform = `scale(${scaleAmtX}, ${scaleAmtY})`;
};
const App = ()=>{
const scaledWrapper=useRef();
const scaledContent=useRef();
useEffect(()=>{
if (scaledWrapper.current && scaledContent.current) {
applyScaling(scaledWrapper.current, scaledContent.current);
}
},[scaledWrapper.current,scaledContent.current]);
return(
<div ref={scaledWrapper}>
<div ref={scaledContent}></div>
</div>
);
}
scale() is relative to the size of the current container.
height and width are relative to the size of the parent container.
In this case, I would simply set both height and width to 100% to fill the parent DOM element. If you must use scale - try 100%. After setting height and width.

Changing text color depending on background

I have a background that animates (it is not a solid color, but actually a canvas animation), but will at each "end" have two specific colors. I would like some text that sits on top of the animation to change color such that when it sits on top of the lighter of the two it is black, and on top of the darker of the two it is white.
To illustrate, see simplified snippet below with simpler animated background (it's not too far from black, but quite far from white)
window.addEventListener("load", function() {
var ctx = c.getContext("2d");
function draw(timestamp) {
ctx.fillStyle = "#BDDAE4";
ctx.fillRect(0, 0, c.width, c.height);
var x = (timestamp / 50) % (c.width + 40) - 40;
ctx.fillStyle = "#79B3C9";
ctx.fillRect(x, 40, 40, c.height - 40);
ctx.fillRect(0, c.height - 40, c.width, c.height);
window.requestAnimationFrame(draw);
}
window.requestAnimationFrame(draw);
}, false);
* {
margin: 0;
padding: 0;
}
div {
overflow: scroll;
height: 40px;
position: relative;
}
div p {
font-size: 2em;
mix-blend-mode: difference;
color: white;
position: fixed;
pointer-events: none;
top: 0;
}
<div>
<canvas id="c" height="120" width="100"></canvas>
<p>TEXT</p>
</div>
<p>(Scroll on the text to see)</p>
I figure that if there is a way, it will include mix-blend-mode and/or filter.

How can I make this canvas rectangle as sharp as a css div

My pen: http://codepen.io/anon/pen/mBliE
The orange css div with the same orange background and the same black border looks sharp and clean.
The orange rectangle drawn in a canvas looks blurry/distorted.
Why is there a difference? I do not set the canvas height/width with a css style which is a
beginner failure I have heard ;-)
How can I make my canvas sharp looking like css?
<canvas id="mycanvas" height="200" width="400"></canvas>
<div></div>
div{
border:black 2px solid;
height:198px;
width:50px;
background:orange;
display:inline-block;
}
canvas {
background: red;
}
$(document).ready(function() {
var canvas = document.getElementById("mycanvas");
var context = canvas.getContext("2d");
context.save();
context.lineWidth = 1;
context.fillStyle = "orange";
context.fillRect(348, 1, 50, 198);
context.strokeStyle = "black";
context.strokeRect(348, 1, 50, 198);
context.fill();
context.restore();
});
You could just play a little bit with you line width:
$(document).ready(function() {
var canvas = document.getElementById("mycanvas");
var context = canvas.getContext("2d");
context.save();
context.lineWidth = 2;
context.fillStyle = "orange";
context.fillRect(350, 1, 50, 198);
context.strokeStyle = "black";
context.strokeRect(349, 1, 50, 198);
context.fill();
context.restore();
});
CodePen
It looks like the stroke is centered on the canvas edges and not located inside or outside, and is set to two px in width as a minimum. By setting the stroke to two - context.lineWidth = 2; - the line gets sharper. Maybe you could somehow move the stroke outside or insede the canvas...
as Ken mentioned...

Vertical and horizontal centering [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Best way to center a <div> on a page vertically and horizontally?
I have a canvas element, and I'd like it to be right in the very center of a page, both vertical and horizontal.
I'm a programmer, and I don't much about CSS. Can anybody help me with centering my canvas both vertically and horizontally?
Here's my current CSS code:
/* Set up canvas style */
#canvas {
font-family: 'pixel';
margin-left: auto;
margin-right: auto;
display: block;
border: 1px solid black;
cursor: none;
outline: none;
}
It's already being centered horizontally, but not vertically, thank you in advance!
this should do the trick:
#canvas {
position: absolute;
top: 50%; left: 50%;
width: 200px;
height: 200px;
margin: -100px 0 0 -100px;
}
The margin top and left has to be negative half the height and width of the element.
The same principal applies if you don't know the width and height and need to calculate it with javascript. Just get the width/height, divide those by half and set the values as a margin in the same way as the example above.
Hope that helps :)
I don't know if this would help, but I wrote this jQuery plugin that might help. I also know this script needs adjustments. It'd adjust the page when needed.
(function($){
$.fn.verticalCenter = function(){
var element = this;
$(element).ready(function(){
changeCss();
$(window).bind("resize", function(){
changeCss();
});
function changeCss(){
var elementHeight = element.height();
var windowHeight = $(window).height();
if(windowHeight > elementHeight)
{
$(element).css({
"position" : 'absolute',
"top" : (windowHeight/2 - elementHeight/2) + "px",
"left" : 0 + "px",
'width' : '100%'
});
}
};
});
};
})(jQuery);
$("#canvas").verticalCenter();
Refined Code + Demo
Please, view this demo in "Full page" mode.
(function($) {
$.fn.verticalCenter = function(watcher) {
var $el = this;
var $watcher = $(watcher);
$el.ready(function() {
_changeCss($el, $watcher);
$watcher.bind("resize", function() {
_changeCss($el, $watcher);
});
});
};
function _changeCss($self, $container) {
var w = $self.width();
var h = $self.height();
var dw = $container.width();
var dh = $container.height();
if (dh > h) {
$self.css({
position: 'absolute',
top: (dh / 2 - h / 2) + 'px',
left: (dw / 2 - w / 2) + 'px',
width: w
});
}
}
})(jQuery);
$("#canvas").verticalCenter(window);
$(function() {
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
drawSmile(ctx, 0.9, 'yellow', 'black', 0.75);
});
function drawSmile(ctx, scale, color1, color2, smilePercent) {
var x = 0, y = 0;
var radius = canvas.width / 2 * scale;
var eyeRadius = radius * 0.12;
var eyeXOffset = radius * 0.4;
var eyeYOffset = radius * 0.33;
ctx.save();
ctx.translate(canvas.width / 2, canvas.height / 2);
ctx.beginPath(); // Draw the face
ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = color1;
ctx.fill();
ctx.lineWidth = radius * 0.05;
ctx.strokeStyle = color2;
ctx.stroke();
ctx.beginPath(); // Draw the eyes
ctx.arc(x - eyeXOffset, y - eyeYOffset, eyeRadius, 0, 2 * Math.PI, false);
ctx.arc(x + eyeXOffset, y - eyeYOffset, eyeRadius, 0, 2 * Math.PI, false);
ctx.fillStyle = color2;
ctx.fill();
ctx.beginPath(); // Draw the mouth
ctx.arc(0, 0, radius * 0.67, Math.PI * (1 - smilePercent), Math.PI * smilePercent, false);
ctx.stroke();
ctx.restore();
}
#canvas {
border: 3px dashed #AAA;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<canvas id="canvas" width="256" height="256"></canvas>

Centering a canvas

How do I markup a page with an HTML5 canvas such that the canvas
Takes up 80% of the width
Has a corresponding pixel height and width which effectively define the ratio (and are proportionally maintained when the canvas is stretched to 80%)
Is centered both vertically and horizontally
You can assume that the canvas is the only thing on the page, but feel free to encapsulate it in divs if necessary.
This will center the canvas horizontally:
#canvas-container {
width: 100%;
text-align:center;
}
canvas {
display: inline;
}
HTML:
<div id="canvas-container">
<canvas>Your browser doesn't support canvas</canvas>
</div>
Looking at the current answers I feel that one easy and clean fix is missing. Just in case someone passes by and looks for the right solution.
I am quite successful with some simple CSS and javascript.
Center canvas to middle of the screen or parent element. No wrapping.
HTML:
<canvas id="canvas" width="400" height="300">No canvas support</canvas>
CSS:
#canvas {
position: absolute;
top:0;
bottom: 0;
left: 0;
right: 0;
margin:auto;
}
Javascript:
window.onload = window.onresize = function() {
var canvas = document.getElementById('canvas');
canvas.width = window.innerWidth * 0.8;
canvas.height = window.innerHeight * 0.8;
}
Works like a charm - tested: firefox, chrome
fiddle: http://jsfiddle.net/djwave28/j6cffppa/3/
easiest way
put the canvas into paragraph tags like this:
<p align="center">
<canvas id="myCanvas" style="background:#220000" width="700" height="500" align="right"></canvas>
</p>
Tested only on Firefox:
<script>
window.onload = window.onresize = function() {
var C = 0.8; // canvas width to viewport width ratio
var W_TO_H = 2/1; // canvas width to canvas height ratio
var el = document.getElementById("a");
// For IE compatibility http://www.google.com/search?q=get+viewport+size+js
var viewportWidth = window.innerWidth;
var viewportHeight = window.innerHeight;
var canvasWidth = viewportWidth * C;
var canvasHeight = canvasWidth / W_TO_H;
el.style.position = "fixed";
el.setAttribute("width", canvasWidth);
el.setAttribute("height", canvasHeight);
el.style.top = (viewportHeight - canvasHeight) / 2;
el.style.left = (viewportWidth - canvasWidth) / 2;
window.ctx = el.getContext("2d");
ctx.clearRect(0,0,canvasWidth,canvasHeight);
ctx.fillStyle = 'yellow';
ctx.moveTo(0, canvasHeight/2);
ctx.lineTo(canvasWidth/2, 0);
ctx.lineTo(canvasWidth, canvasHeight/2);
ctx.lineTo(canvasWidth/2, canvasHeight);
ctx.lineTo(0, canvasHeight/2);
ctx.fill()
}
</script>
<body>
<canvas id="a" style="background: black">
</canvas>
</body>
in order to center the canvas within the window +"px" should be added to el.style.top and el.style.left.
el.style.top = (viewportHeight - canvasHeight) / 2 +"px";
el.style.left = (viewportWidth - canvasWidth) / 2 +"px";
Resizing canvas using css is not a good idea. It should be done using Javascript. See the below function which does it
function setCanvas(){
var canvasNode = document.getElementById('xCanvas');
var pw = canvasNode.parentNode.clientWidth;
var ph = canvasNode.parentNode.clientHeight;
canvasNode.height = pw * 0.8 * (canvasNode.height/canvasNode.width);
canvasNode.width = pw * 0.8;
canvasNode.style.top = (ph-canvasNode.height)/2 + "px";
canvasNode.style.left = (pw-canvasNode.width)/2 + "px";
}
demo here : http://jsfiddle.net/9Rmwt/11/show/
.
Simple:
<body>
<div>
<div style="width: 800px; height:500px; margin: 50px auto;">
<canvas width="800" height="500" style="background:#CCC">
Your browser does not support HTML5 Canvas.
</canvas>
</div>
</div>
</body>
Given that canvas is nothing without JavaScript, use JavaScript too for sizing and positionning (you know: onresize, position:absolute, etc.)
As to the CSS suggestion:
#myCanvas {
width: 100%;
height: 100%;
}
By the standard, CSS does not size the canvas coordinate system, it scales the content. In Chrome, the CSS mentioned will scale the canvas up or down to fit the browser's layout. In the typical case where the coordinate system is smaller than the browser's dimensions in pixels, this effectively lowers the resolution of your drawing. It most likely results in non-proportional drawing as well.
Same codes from Nickolay above, but tested on IE9 and chrome (and removed the extra rendering):
window.onload = window.onresize = function() {
var canvas = document.getElementById('canvas');
var viewportWidth = window.innerWidth;
var viewportHeight = window.innerHeight;
var canvasWidth = viewportWidth * 0.8;
var canvasHeight = canvasWidth / 2;
canvas.style.position = "absolute";
canvas.setAttribute("width", canvasWidth);
canvas.setAttribute("height", canvasHeight);
canvas.style.top = (viewportHeight - canvasHeight) / 2 + "px";
canvas.style.left = (viewportWidth - canvasWidth) / 2 + "px";
}
HTML:
<body>
<canvas id="canvas" style="background: #ffffff">
Canvas is not supported.
</canvas>
</body>
The top and left offset only works when I add px.
Make a line in the center and make it transparent.
This line will be the fulcrum to center the content in the canvas
var canvas = document.getElementById('canvas');
var context = canvas.getContext('2d');
context.strokeStyle = 'transparent';
context.moveTo(width/2, 0);
context.lineTo(width/2, height);
context.stroke();
context.textAlign = 'center';
with width height being the size of the html canvas
Wrapping it with div should work. I tested it in Firefox, Chrome on Fedora 13 (demo).
#content {
width: 95%;
height: 95%;
margin: auto;
}
#myCanvas {
width: 100%;
height: 100%;
border: 1px solid black;
}
And the canvas should be enclosed in tag
<div id="content">
<canvas id="myCanvas">Your browser doesn't support canvas tag</canvas>
</div>
Let me know if it works. Cheers.

Resources