Canvas shadow / glow effect on a semi-transparent shape? - html

I have a semi-transparent shape:
this.cx.beginPath();
this.cx.globalAlpha = .1;
this.cx.fillStyle = gradientFill;
this.cx.strokeStyle = gradientStroke;
this.cx.arc(150, 150, 139, 0, Math.PI * 2, true);
this.cx.lineWidth = 1;
this.cx.stroke();
this.cx.fill();
I want to add a bit of shadow, but I want it to only appear outside of the shape, I guess more of a glow than a shadow. Is there a way to do this in canvas as my attempts with:
this.cx.shadowColor = 'rgba(0, 0, 0, .75)';
this.cx.shadowBlur = 5;
this.cx.shadowOffsetX = 5;
this.cx.shadowOffsetY = -5;
Look fairy ordinary as the dark shadow is visible through the semi-transparent shape.
Thanks!

One way is to use globalCompositeOperations in order to keep only your outer shadow first, and then redraw your semi-transparent parts over it.
But note that you'll have a lot of artifact noise...
(async function() {
var ctx = c.getContext('2d');
// commons
var gradientFill = ctx.createLinearGradient(0, 0, 200, 0);
gradientFill.addColorStop(0, 'rgba(0,0,0,0)')
gradientFill.addColorStop(1, 'rgba(255,0,0,1)')
var gradientStroke = ctx.createLinearGradient(0, 0, 200, 0);
gradientStroke.addColorStop(0, 'rgba(0,0,0,0)')
gradientStroke.addColorStop(1, 'rgba(0,255,0,1)')
ctx.lineWidth = 5;
// needed only once
ctx.beginPath();
ctx.arc(150, 150, 139, 0, Math.PI * 2, true);
await wait(1000); // simply to show each step
// firt we draw only the shadow with black fill and stroke
ctx.shadowColor = 'rgba(0, 0, 0, .75)';
ctx.shadowBlur = 5;
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = -5;
ctx.stroke();
ctx.fill();
await wait(1000);
// then keep only the shadow
ctx.globalCompositeOperation = 'destination-out'; // will erase existing content at drawn position
ctx.shadowColor = 'transparent'; // remove the shadow
ctx.stroke();
ctx.fill();
await wait(1000);
// finally draw the semi-transparent version
ctx.globalCompositeOperation = 'source-over';
ctx.globalAlpha = .1;
ctx.fillStyle = gradientFill;
ctx.strokeStyle = gradientStroke;
ctx.stroke();
ctx.fill();
})();
function wait(t) {
return new Promise(r => setTimeout(r, t))
}
<canvas id="c" height="300"></canvas>

Related

HTML canvas: How to achieve dropshadow on clipped object?

I am trying to draw a dropshadow on a clipped image.
function drawCircle(ctx,w,h) {
ctx.beginPath();
ctx.ellipse(w/2, h/2, w/2, h/2, 0, 2 * Math.PI, false);
//ctx.fill(); // This helps, but not suitable for transparent images!
}
const canvas = document.getElementById('c');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = () => {
// Draw ellipse & clip (removing these 2 lines makes the dropshadow appear)
drawCircle(ctx, img.width,img.height);
ctx.clip();
ctx.shadowOffsetX = 4;
ctx.shadowOffsetY = 4;
ctx.shadowBlur = 4;
ctx.shadowColor = '#000';
ctx.clip();
ctx.drawImage(img, 0, 0);
}
img.src = 'https://i.imgur.com/NdrmFsr.jpg';
Without clipping, the image on the canvas has a very visible drop shadow.
With clipping applied, the drop shadow is not visible.
What do I need to change to make the drop shadow visible on a clipped image?
Fiddle is here: https://jsfiddle.net/av01d/xdemLs2u/
Using the css property filter
filter: drop-shadow(10px 6px 3px rgba(50, 50, 0, 1));
https://jsfiddle.net/nj2rpxz6/
Here is the mozzila doc about the drop-shadow() CSS function
Edit :
Here is how you can add css with Javascript :
const canvas = document.getElementById('c');
canvas.style.filter = "drop-shadow(10px 6px 3px rgba(50, 50, 0, 1))"
I found a solution. Draw the clipped object on a temporary canvas, then draw that canvas with shadow applied to the main canvas.
function drawCircle(ctx,w,h) {
ctx.beginPath();
ctx.ellipse(w/2, h/2, w/2, h/2, 0, 2 * Math.PI, false);
}
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
var img = new Image();
img.onload = () => {
ctx.save();
drawCircle(ctx, img.width,img.height);
ctx.clip();
ctx.drawImage(img, 0, 0);
ctx.restore();
const canvas2 = document.createElement('canvas'), ctx2 = canvas2.getContext('2d');
canvas2.width = canvas.width; canvas2.height = canvas.height;
ctx2.shadowOffsetX = 4;
ctx2.shadowOffsetY = 4;
ctx2.shadowBlur = 4;
ctx2.shadowColor = '#000';
ctx2.drawImage(canvas,0,0);
//ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.drawImage(canvas2,0,0);
}
img.src = 'https://i.imgur.com/NdrmFsr.jpg';

Drawing non-gradient circle with colorstop

How can I draw a non-gradient circle with colorstop, something like this:
The closest I got was using radial gradient http://jsfiddle.net/8tdz0bo4/2/:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var x = 100,
y = 75,
innerRadius = 1,
outerRadius = 50,
radius = 60;
var gradient = ctx.createRadialGradient(x, y, innerRadius, x, y, outerRadius);
gradient.addColorStop(0, 'red');
gradient.addColorStop(1, 'transparent');
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fillStyle = gradient;
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.stroke();
Answer is quite simple : to avoid any gradient, just build several steps having same start and end color like in :
0.0 red // first red step
0.5 red // end of first red step
0.5 blue // second blue step
1.0 blue. // end of blue step
With this idea, your code becomes :
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
var x = 100,
y = 75,
innerRadius = 1,
outerRadius = 50,
radius = 60;
var gradient = ctx.createRadialGradient(x, y, innerRadius, x, y, outerRadius);
gradient.addColorStop(0, 'red');
gradient.addColorStop(0.6, 'red');
gradient.addColorStop(0.6, 'transparent');
gradient.addColorStop(1, 'transparent');
ctx.arc(x, y, radius, 0, 2 * Math.PI);
ctx.fillStyle = gradient;
ctx.fill();
ctx.lineWidth = 2;
ctx.strokeStyle = 'black';
ctx.stroke();
<canvas id='canvas'></canvas>
Add these
gradient.addColorStop(0.2, 'red');
gradient.addColorStop(0.2, 'transparent');
http://jsfiddle.net/8tdz0bo4/3/

Canvas transparency mask effect reset

I'm trying to overlay a canvas on a page in an attempt to make it look like it has been frosted over and then use mousemove to clear it away, following this post:
http://www.stevecalderon.com/2012/03/html5-canvas-tricks-transparency-mask-for-eraser-effect.html
I have created a jsfiddle - http://jsfiddle.net/ZWsG6/2/
var canvas = "";
var ctx = "";
function frost2(){
var int=self.setInterval(function(){clock()},1000);
canvas = document.createElement("canvas");
canvas.width = window.innerWidth;
canvas.height= window.innerHeight;
canvas.style.position = "absolute";
canvas.style.top = "0px";
canvas.style.left = "0px";
var objTo = document.getElementById("fltouch");
objTo.appendChild(canvas);
ctx = canvas.getContext('2d');
ctx.globalAlpha = 0.95;
ctx.fillStyle = "rgb(0, 255, 255)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'destination-out';
function drawPoint(pointX,pointY){
///scrape
var grd = ctx.createLinearGradient(pointX - 75,pointY-5,pointX + 75,pointY + 5)
grd.addColorStop(0, "transparent");
grd.addColorStop(0.3, "rgba(255,255,255,.6)");
grd.addColorStop(0.7, "rgba(255,255,255,.6)");
grd.addColorStop(1, "transparent");
ctx.fillStyle = grd;
ctx.fillRect(pointX - 75,pointY,400,30);
}
canvas.addEventListener('mousemove',function(e){
e.preventDefault();
drawPoint(e.pageX,e.pageY);
},false);
}
function clock(){
ctx.globalAlpha = 0.95;
ctx.fillStyle = "rgb(0, 255, 255)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'destination-out';
}
I've added a function clock() to run every second and refill the canvas with the colour so that it needs to be cleared again but it doesn't work, only remove the fill altogether.
Can anyone please tell how to reset the canvas to be filled with colour again? If it could be done slowly to look sorta like it is freezing over again that would be a bonus.
You need to set globalCompositeOperation back to default source-over value before refilling a canvas:
function clock() {
ctx.globalCompositeOperation = 'source-over';
ctx.globalAlpha = 0.95;
ctx.fillStyle = "rgb(0, 255, 255)";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = 'destination-out';
}
http://jsfiddle.net/VqCbv/1/

Draw rectancle with border without filling in html5

I am trying to create a grid in the canvas as below
var canvas = $("#canvas");
var ctx = canvas.get(0).getContext("2d");
ctx.strokeStyle = "rgb(0, 0, 0)";
for(x=0;x<=300;x++){
for(y=0;y<=300;y++){
ctx.strokeRect(x, y, 20, 20);
}
}
but it just fills the HTML5 canvas instead of drawing squares with border. Can someone enlighten me on what I'm doing wrong here?
I re-wrote it like this
var canvas = $("#myCanvas");
var context = canvas.get(0).getContext("2d");
context.strokeStyle = "rgb(255, 0, 0)";
for(x=0; x<5; x++){
for(y=0; y<5; y++){
context.strokeRect(y*20, x*20, 20, 20);
}
}

Canvas shapes becoming aliased when re-drawn in safari

I'm drawing a simple progress indicator using canvas. When the element is drawn for the first time it looks all nice and anti-aliased, but when drawn a second time, it loses it's anti-aliasing. Anyone know what could be going on here?
function drawProgress(id, percent) {
var selected = $(safeID(id)).is('.selected');
var canvas = $(safeID("CANVAS_" + id));
var ctx = $(canvas)[0].getContext('2d');
ctx.clearRect();
if ( selected ) {
ctx.fillStyle = "#ffffff";
ctx.strokeStyle = "#ffffff";
}
else {
ctx.fillStyle = "#99a7ca";
ctx.strokeStyle = "#99a7ca";
}
ctx.beginPath();
ctx.arc(canvas.width()/2.0, canvas.height()/2.0, canvas.width()/2.0-1, 0, Math.PI, false);
ctx.fill();
ctx.beginPath();
ctx.arc(canvas.width()/2.0, canvas.height()/2.0, canvas.width()/2.0-1, 0, Math.PI*2.0, false);
ctx.stroke();
}
You need to specify dimensions to clearRect.