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
Related
I'm trying to create a blurry donut shape like this on a canvas with js.
I tried
gradient.addColorStop(0, "rgba(0, 0, 0, .1)");
gradient.addColorStop(0.5, "rgba(128, 128, 128, .1)");
gradient.addColorStop(1, "rgba(0, 0, 0, .1)");
But I only get
It has a distinct defined circle at the stop radius. What I want is a smooth falloff. Something like this..
Is this possible?
It looks like you're after a Gaussian blur rather than a gradient.
You can create such a blur using the ctx.filter property and pass in a CSS filter value "blur(Npx)".
However Safari still doesn't support this property, so for this browser, we need to use a shadow as a workaround.
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
if (ctx.filter === "none") {
ctx.filter = "blur(60px)";
}
else { // Safari still doesn't support ctx.filter...
ctx.shadowColor = "#34aaff";
ctx.shadowBlur = 120; // x2
ctx.shadowOffsetX = 800;
ctx.translate(-800, 0); // we draw the actual shape outside of the visible context
}
ctx.arc(400, 400, 200, 0, Math.PI*2);
ctx.lineWidth = 125;
ctx.strokeStyle = "#34aaff";
ctx.stroke();
<canvas width=800 height=800></canvas>
You can try to add one or more color stops to control the shape:
gradient.addColorStop(0, "White");
gradient.addColorStop(0.3, "rgba(128, 128, 256, .5)");
gradient.addColorStop(0.4, "rgba(128, 128, 256, .5)");
gradient.addColorStop(1, "White");
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'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>
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'm trying to change fillstyle on focus in and focus out event. When the event call multiple time it's overlapping. Here my code:
function FillColorToFields(id, isOnFocus) {
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
var coordinates = $('#hidCoord_' + id).val().split(',');
if (isOnFocus) {
context.fillStyle = "rgba(0,255,0,0.1)";
context.fillRect(coordinates[0], coordinates[1], coordinates[2], coordinates[3]);
} else {
context.fillStyle = "rgba(255, 255, 255, 0.5)";
context.fillRect(coordinates[0], coordinates[1], coordinates[2], coordinates[3]);
}
}
Simply use context.clearRect(x, y, width, height). Its purpose is to erase any previously drawn content of the rectangle (check out the documentation). See this forked fiddle for an example.
I set the image as canvas background image instead of drawing it to canvas. It's working fine now.
http://jsfiddle.net/vm47p2sk/8/
<canvas id="myCanvas"></canvas>
<a id = "green" href="#">On focus</a>
<a id = "transparent" href="#">On focus out</a>
$('#myCanvas').css("background-image", "url(https://www.webkit.org/blog-files/acid3-100.png)");
var canvas = document.getElementById('myCanvas');
var context = canvas.getContext('2d');
context.strokeStyle = "#009900";
context.lineWidth = 3;
context.strokeRect (5, 5, 100, 100);
$('#green').click(function (){
context.clearRect (8, 8, 94, 94);
context.fillStyle = "rgba(0,255,0,0.1)";
context.fillRect (8, 8, 94, 94);
});
$('#transparent').click(function (){
context.clearRect (8, 8, 94, 94);
context.fillStyle = "rgba(255, 255, 255, 0.1)";
context.fillRect (8, 8, 94, 94);
});