Make canvas with shape responsive - javascript

I created a canvas with a circle inside and filled it with a gradient.
I am not sure how to make the canvas as well as the circle inside the canvas responsive to the screen size.
I tried using vh and vw for the width and height of the canvas. But when I change the height or width of the window, the circle looks either too long or too wide.
Question:
I want the entire circle to decrease in size when the window size is reduced. I am not sure how to do this.
Code:
var c = document.getElementById('canvassun');
var ctx = c.getContext('2d');
var grd = ctx.createRadialGradient(85, 85, 20, 85, 85, 70);
grd.addColorStop(0, 'red');
grd.addColorStop(0.5, 'orange');
grd.addColorStop(0.8, 'yellow');
grd.addColorStop(1, 'white');
ctx.beginPath();
ctx.fillStyle = grd;
ctx.arc(90, 90, 70, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath;
#canvassun {
height: 30vh;
width: 14vw;
border: 1px solid black;
margin: 0 auto;
display: block;
margin-top: 18%;
}
<canvas id="canvassun" width=170 height=170></canvas>

Listen to window resize event and redraw the canvas
function draw() {
var c = document.getElementById('canvassun');
var ctx = c.getContext('2d');
var grd = ctx.createRadialGradient(85, 85, 20, 85, 85, 70);
grd.addColorStop(0, 'red');
grd.addColorStop(0.5, 'orange');
grd.addColorStop(0.8, 'yellow');
grd.addColorStop(1, 'white');
ctx.beginPath();
ctx.fillStyle = grd;
ctx.arc(90, 90, 70, 0, 2 * Math.PI);
ctx.fill();
ctx.closePath;
}
window.addEventListener("resize", draw);
Note Probably you need to debounce the draw function for performance

Related

Is there a way to disable color mixing/overlapping in html canvas

I have a canvas and when i draw 2 things overlapping with low opacity, the opacity gets higher in the part where they are overlapping. Is there a way to make it all the same opacity even if 2 things are overlapping.
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
ctx.fillStyle = "rgba(0, 0, 255, 0.2)";
ctx.fillRect(10, 10, 50, 50);
ctx.fillRect(20, 20, 50, 60);
ctx.fillRect(40, 5, 50, 40)
canvas {
width: 100vw;
height: 100vh;
}
<p>all of it should be the same color but the overlapping parts are darker</p>
<canvas id="canvas"></canvas>
For simple cases the usual trick is to use a second canvas as a layer: you draw the parts that shouldn't mix with full opacity on a detached canvas, and then you draw this canvas with the intended alpha on the visible one:
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const detached = canvas.cloneNode();
const ctx2 = detached.getContext("2d");
// draw at full opacity
ctx2.fillStyle = "rgb(0, 0, 255)";
ctx2.fillRect(10, 10, 50, 50);
ctx2.fillRect(20, 20, 50, 60);
ctx2.fillRect(40, 5, 50, 40)
// draw something in the background of the visible canvas
// where we want the blue rects to mix with
ctx.fillStyle = "green";
ctx.fillRect(50, 65, 30, 30);
// now draw the blue rects in a single pass with the expected alpha
ctx.globalAlpha = 0.2
ctx.drawImage(detached, 0, 0);
/* CSS checkerboard stolen from https://drafts.csswg.org/css-images-4/#example-2de97f53 */
canvas {
background: repeating-conic-gradient(rgba(0,0,0,0.1) 0deg 25%, white 0deg 50%);
background-size: 2em 2em;
}
<canvas></canvas>
Note that this exact example could have been done with a single canvas: if all your shape do share the same color, you can make them all be part of the same sub-path and fill them all in a single call.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.fillStyle = "green";
ctx.fillRect(50, 65, 30, 30);
ctx.fillStyle = "rgb(0, 0, 255, 0.2)";
ctx.beginPath();
ctx.rect(10, 10, 50, 50);
ctx.rect(20, 20, 50, 60);
ctx.rect(40, 5, 50, 40);
ctx.fill();
/* CSS checkerboard stolen from https://drafts.csswg.org/css-images-4/#example-2de97f53 */
canvas {
background: repeating-conic-gradient(rgba(0,0,0,0.1) 0deg 25%, white 0deg 50%);
background-size: 2em 2em;
}
<canvas></canvas>
Now these work only when the transparency is the same for all the shapes to be drawn. In case of multiple shapes with different alpha values, they'd still get mixed.
Here is an example of such a case.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.fillStyle = "green";
ctx.fillRect(50, 10, 50, 60);
ctx.fillStyle = "rgba(0, 0, 255, .2)";
ctx.fillRect(40, 50, 70, 60);
ctx.fillStyle = "rgba(0, 0, 255, .8)";
ctx.fillRect(10, 20, 60, 70);
/* CSS checkerboard stolen from https://drafts.csswg.org/css-images-4/#example-2de97f53 */
canvas {
background: repeating-conic-gradient(rgba(0,0,0,0.1) 0deg 25%, white 0deg 50%);
background-size: 2em 2em;
}
<canvas></canvas>
If we wanted the first blue rect to be over the second one without mixing, but still mix with the green one, we'd need to use a variation of the first solution, but use more steps:
First you calculate the alpha value of the more transparent rect relative to the more opaque one. Here we have 0.2 and 0.8, so if we want the 0.8 to be 1, 0.2 has to become 0.25.
We draw the more transparent rect at 0.25, then the more opaque one at 1 over this.
We redraw the composition with the target 0.8 alpha value.
We draw that over the green background.
However I'll take the opportunity of this new snippet to show that with some creative use of the globalCompositeOperation property we can make all this on a single canvas.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
// To use a single canvas, we will draw the green "background" later
// target color is rgb(0, 0, 255, 0.2) & max alpha is 0.8
// 0.2 x (1 / 0.8) -> 0.25
ctx.fillStyle = "rgb(0, 0, 255, 0.25)";
ctx.fillRect(40, 50, 70, 60);
ctx.fillStyle = "rgba(0, 0, 255, 1)";
ctx.fillRect(10, 20, 60, 70);
ctx.globalAlpha = 0.8;
// gCO "copy" will clear whatever was on the context before the next paint
ctx.globalCompositeOperation = "copy";
ctx.drawImage(ctx.canvas, 0, 0);
// we could continue over like this if more such opacities were required
ctx.globalAlpha = 1;
// gCO "destination-over" will draw behind what's already painted on the context
ctx.globalCompositeOperation = "destination-over";
ctx.fillStyle = "green";
ctx.fillRect(50, 10, 50, 60);
/* CSS checkerboard stolen from https://drafts.csswg.org/css-images-4/#example-2de97f53 */
canvas {
background: repeating-conic-gradient(rgba(0,0,0,0.1) 0deg 25%, white 0deg 50%);
background-size: 2em 2em;
}
<canvas></canvas>
Now, it may be of interest for some, but at WHATWG/html we started a discussion about enabling a layer API for the canvas2D, and while it's far from reaching a consensus, I wrote a not so little prototype of a CanvasLayer interface that would allow us to, well, use layers in a canvas.
The idea is to create a CanvasLayer object, that does record drawings operations that will get executed on it when it's rendered on a context, using the context's current settings. Basically reproducing the detached canvas trick, but with taking care automagically of the sizing of the detached canvas, and (if implemented natively), without assigning a full bitmap buffer.
In my very biased opinion (I'm the author of both that proposal and of the prototype), this would allow for clearer code when we have to deal with multiple layers over a canvas.
/* CSS checkerboard stolen from https://drafts.csswg.org/css-images-4/#example-2de97f53 */
canvas {
background: repeating-conic-gradient(rgba(0,0,0,0.1) 0deg 25%, white 0deg 50%);
background-size: 2em 2em;
}
<canvas></canvas>
<script type="module">
import CanvasLayer from "https://cdn.jsdelivr.net/gh/Kaiido/CanvasLayer/bundles/CanvasLayer.min.mjs";
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
ctx.fillStyle = "green";
ctx.fillRect(50, 10, 50, 60);
const backLayer = new CanvasLayer();
backLayer.fillStyle = "rgba(0, 0, 255)"; // fully opaque
backLayer.fillRect(40, 50, 70, 60);
const frontLayer = new CanvasLayer();
frontLayer.fillStyle = "rgba(0, 0, 255)";
frontLayer.fillRect(10, 20, 60, 70);
frontLayer.globalAlpha = 0.2;
frontLayer.renderLayer(backLayer);
ctx.globalAlpha = 0.8;
ctx.renderLayer(frontLayer);
</script>

transparent circle on opaque canvas

I'm trying to draw a transparent circle on the HTML Canvas. Here is the code:
const canvas = document.querySelector('#canvas');
let ctx = canvas.getContext('2d');
canvas.width = 700;
canvas.height = 700;
ctx.beginPath();
ctx.fillStyle = 'rgba(0, 0, 0, 0.5)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'destination-out';
ctx.clearRect(50, 50, 200, 200);
ctx.beginPath();
ctx.fillRect(50, 50, 200, 200);
ctx.moveTo(350, 150);
ctx.beginPath();
ctx.arc(350, 150, 80, 0, 2 * Math.PI, false);
ctx.closePath();
ctx.fill();
I was able to make the rectangle transparent with globalCompositeOperation set to destination-out but it failed to make the circle fully transparent.
Here is what MDN says about destination-out operation,
The existing content is kept where it doesn't overlap the new shape.
The circle has still some opaque color applied to it. How to make it fully transparent?
Here is the live demo.
Unable to understand what's wrong with the code. Any help would be appreciated.
You are using beginpath methods but the directives you use aren't paths.
The fully transparent rectangle is created by the clearRect and not by the fillRect method.
So you should set fillStyle to white and then the code works.
const canvas = document.querySelector('#canvas');
let ctx = canvas.getContext('2d');
canvas.width = 600;
canvas.height = 400;
ctx.fillStyle = 'rgba(0, 0, 0, .5)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'destination-out';
ctx.fillStyle = 'rgba(255, 255, 255, 1)';
ctx.fillRect(25, 25, 150, 150);
ctx.arc(275, 100, 60, 0, 2 * Math.PI, false);
ctx.fill();
body {
background-color: skyblue;
position: relative;
}
img, #canvas {
position: absolute;
}
<body>
<img src="https://images.unsplash.com/photo-1635315850978-44cd0b237006?ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&ixlib=rb-1.2.1&auto=format&fit=crop&w=1770&q=80" alt="" width=600 height=390>
<canvas id="canvas"></canvas>
</body>

Canvas radial gradient to match css radial gradient

I've created two radial gradients that I would expect to be very similar: one using canvas operations and one using the css linear gradient. However, they end up looking very different. Is there any way to get the canvas gradient to match the css gradient, or is this simply an implementation difference that is too hard to work around?
const canvas = document.getElementById('bar');
const context = canvas.getContext('2d');
canvas.width = 500;
canvas.height = 500;
const gradient = context.createRadialGradient(250, 250, 0, 250, 250, 250);
// Add the color stops
gradient.addColorStop(0, 'rgba(246,238,25,1)');
gradient.addColorStop(1, 'rgba(17,94,222,0.3)');
// Set the fill style and draw a rectangle
context.fillStyle = gradient;
context.fillRect(0, 0, 500, 500);
#foo {
width: 500px;
height: 500px;
background: radial-gradient(circle, rgba(246, 238, 25, 1) 0, rgba(17, 94, 222, 0.3) 100%);
}
<div id="foo">
</div>
<canvas id="bar"></canvas>

Partially filling a rectangle in HTML5

I have a canvas in which I want to draw multiple rectangles (of varying sizes).
These rectangles have to be filled or emptied over time, be it automatically or by input from the user.
The problem is that I have never programmed HTML5 before and don't know how to partially fill a rectangle or increase the fill (through something like rectangle.backgroundHeight++).
Here is my code:
<canvas id="myCanvas" height="1000" width="1000" />
<script>
window.addEventListener('load', function () {
var ctx = document.getElementById('myCanvas').getContext('2d');
var interval = setInterval(function() {
return function () {
// Draw large fuel tanks
var fuelA = ctx.rect(150, 60, 110, 130);
var fuelB = ctx.rect(600, 60, 110, 130);
// Draw small fuel tanks
var fuelC = ctx.rect(40, 300, 60, 130);
var fuelD = ctx.rect(300, 300, 60, 130);
var fuelE = ctx.rect(500, 300, 60, 130);
var fuelF = ctx.rect(750, 300, 60, 130);
ctx.stroke();
};
}(), 1000/25);
}, false);
While programming the canvas is a great skill to have, an animation like this can be done completely in CSS, using transitions:
var liquid= document.querySelector('.liquid');
document.querySelector('#bfill').onclick= function() {
liquid.style.height = '100%';
};
document.querySelector('#bempty').onclick= function() {
liquid.style.height = '0%';
};
.tank {
position: absolute;
left: 20px;
top: 40px;
height: 100px;
width: 50px;
border: 1px solid black;
}
.liquid {
transition: height 3s;
position: absolute;
left: 0px;
bottom: 0px;
height: 20%;
width: 100%;
background: blue;
}
<button id="bfill">Fill the tank</button>
<button id="bempty">Empty the tank</button>
<div class="tank">
<div class="liquid"></div>
</div>
I do not think that is possible by using one rectangle, as it can only be filled by a single color, or a gradient - and there is no option for partial fill.
However you may draw a rectangle with only strokes and no fill, and another on top of it, which acts as a fill for the first rectangle. You can size this however you want to.
Edit: Well it seems, you can achieve this with gradients, however I can't really tell which is the better solution.
You could use a gradient to get an effect of partially filling e.g filling a rectangle 50%.
var c = document.getElementById("myCanvas");
var ctx = c.getContext("2d");
var rec = ctx.rect(20, 20, 150, 100);
var grd = ctx.createLinearGradient(0, 0, 190, 0);
grd.addColorStop(0, "black");
grd.addColorStop(0.5, "black");
grd.addColorStop(0.5, "white");
grd.addColorStop(1, "white");
ctx.fillStyle = grd;
ctx.fill();
ctx.strokeStyle = 'black';
ctx.stroke();
<canvas id="myCanvas" />

How to apply canvas to whole document or how to draw on whole document?

I have .aspx page and i want to draw on whole document using JS.I'm newbie in JS...
For example this code(in my aspx) allow me to draw on 200x200 area:
<canvas id="canvas" width="200" height="200"></canvas>
<script type ="text/javascript">
$(document).ready(function () {
ctx = document.getElementById('canvas').getContext('2d');
ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
ctx.fillRect(30, 30, 55, 50);
});
</script>
How to draw on whole document?Because I can't write something like:
<canvas id="canvas" $(document).width() $(document).height()></canvas>
I want to draw a transparent rectangle on whole document and see the page content behind.
Here is the solution(thnx to kirilloid):
CSS:
#canvas {
position: absolute;
left: 0;
top: 0;
}
JS:
function updateCanvas(width,height) {
var $canvas = $("#canvas");
$canvas.attr('width', $(document).width())
$canvas.attr('height', $(document).height());
var ctx = $canvas.get(0).getContext('2d');
ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
ctx.fillRect(0, 0, width, height/2);
ctx.fillStyle = "rgba(0, 200, 0, 0.5)";
ctx.fillRect(0, height/2, width, height);
}
$(document).ready(function () {
updateCanvas($(document).width(), $(document).height())
});
There's no way to draw on the page itself. You'll need to make canvas fit whole window and update its sizes (and redraw) on window resize.
If you change its sizes in CSS, canvas is only stretched like images do. If you change width and height attributes, then it's cleared.
CSS:
#canvas {
position: fixed;
left: 0;
top: 0;
}
The code:
function updateCanvas() {
var $canvas = $("#canvas");
$canvas.attr('width', $(document).width())
$canvas.attr('height', $(document).height());
var ctx = $canvas.get(0).getContext('2d');
ctx.fillStyle = "rgba(0, 0, 200, 0.5)";
ctx.fillRect(30, 30, 55, 50);
}
$(window).on('resize', updateCanvas); // maybe, add some thresholding
updateCanvas();
UPD: Just came up with more performant solution: set canvas sizes from window.screen and put it into overflow: hidden container fitting whole window. Then you won't need to redraw or resize canvas at all (multi-display users can still be a problem).

Categories