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>
Related
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>
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
I am trying to make a radial gradient with a Javascipt/HTML canvas. The problem is that the gradients don't overlap properly as if the alpha channel isn't working.
This is the code I am using:
var gradient1 = ctx.createRadialGradient(300, 300, 300, 300, 300, 0);
gradient1.addColorStop(0,"rgba(0, 0, 0, 0)");
gradient1.addColorStop(1,"#FF0000");
ctx.fillStyle = gradient1;
ctx.fillRect(x1, y1, 600, 600);
ctx.fillRect(x1, y1, 600, 600);
Here is a picture:
This for some reason fades to a black-like color rather than staying red. This leads it to act weird when two of these gradients of different colors are touching.
How can I make this react normally?
Cause
The gradient defined is red-black and both the color and the alpha channel will be interpolated . At 50% it will be halfway between red and black, but also 50% visible which is why it is becoming black-ish.
Cure
To fix make sure both color stops are the same color which just the alpha channel changed. This will keep the color the same all the way:
gradient1.addColorStop(0, "rgba(255, 0, 0, 0)"); // red, but transparent
gradient1.addColorStop(0, "#f00"); // red, solid
Click the link below to see this in action:
var ctx = document.querySelector("canvas").getContext("2d");
var gradient1 = ctx.createRadialGradient(300, 300, 300, 300, 300, 0);
gradient1.addColorStop(0,"rgba(255, 0, 0, 0)");
gradient1.addColorStop(1,"#FF0000");
ctx.fillStyle = gradient1;
ctx.fillRect(0, 0, 600, 600);
ctx.fillRect(0, 0, 600, 600);
<canvas width=600 height=600></canvas>
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" />
I am currently coding an interactive application using Javascript and the HTML canvas element. One of the things I'm coding is an opacity gradient. My gradient is supposed to fade from transparent to partially opaque, but the entire area I defined as the place to create the gradient is only partially opaque with no fade from transparency. Here's my code:
function draw() {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var grd = ctx.createLinearGradient(0, 1000, 0, 0);
grd.addColorStop(0.5, "rgba(255, 255, 255, 0.5)");
grd.addColorStop(1, "rgba(255, 255, 255, 0)");
ctx.fillStyle = grd;
ctx.fillRect(0, 660, 1000, 10);
}
It has something to with the dimensions you use in createLinearGradient and fillRect. Right now you are defining your gradient area as a really tall thin rectangle [(0,0) to (0,1000)]. However, then you draw your gradient rectangle as a pretty big rectangle [(0,660) to (1000,10)].
I'm not exactly sure what your desired output looks like but it seems that your ctx.fillRect line is wrong. Trying the following works for me:
function draw() {
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var grd = ctx.createLinearGradient(0, 1000, 0, 0);
grd.addColorStop(0.5, "rgba(255, 255, 255, 0.5)");
grd.addColorStop(1, "rgba(255, 255, 255, 0)");
ctx.fillStyle = grd;
ctx.fillRect(0, 10, 1000, 660);
}
And the jsFiddle if you are interested