HTML canvas: How to achieve dropshadow on clipped object? - html

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';

Related

Chrome Canvas: Small globalAlpha ignored

On Chromium 90.0.4430.93 (which uses the same rendering engine as Chrome), the following fading effect does not work. Why? And how would one fix it?
It is done by painting over the canvas with another black canvas and a low globalAlpha on each requestAnimationFrame call.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
let start;
let lastTime;
function draw() {
canvas.bgcanvas = document.createElement('canvas');
canvas.bgcanvas.width = canvas.width;
canvas.bgcanvas.height = canvas.height;
bgctx = canvas.bgcanvas.getContext('2d');
bgctx.fillStyle = '#000';
bgctx.fillRect(0, 0, canvas.width, canvas.height);
}
function step(timestamp) {
if (start === undefined)
start = timestamp;
lastTime = timestamp;
const elapsed = timestamp - lastTime;
lastTime = timestamp;
ctx.globalAlpha = 0.0001*elapsed;
ctx.drawImage(canvas.bgcanvas, 0, 0);
ctx.globalAlpha = 1;
var x = (timestamp - start)*0.01 % 200;
var y = 0;
ctx.beginPath();
ctx.fillStyle = '#77f';
ctx.arc(x, canvas.height/2-y, 7, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
window.requestAnimationFrame(step);
}
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
lines = [];
draw();
ctx.drawImage(canvas.bgcanvas, 0, 0);
}
window.addEventListener('resize', resizeCanvas, false);
resizeCanvas();
window.requestAnimationFrame(step);
* { margin:0; padding:0; } /* to remove the top and left whitespace */
body {
width:100%; height:100%;
}
canvas {
display:block;
}
<canvas id="canvas"></canvas>
Result in Firefox:
Result in Chromium:
Thanks to Blindman67's comment, I now understand that it is non-trivial to get the same exponential decay shown in the above figure. But a linear one that works browser independently is relatively simple: Just subtract #010101 on each rendering step via the following code:
ctx.globalCompositeOperation = "difference";
ctx.fillStyle = '#010101';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = "source-over";
To change the speed of decay, one can e.g. only apply this effect every i-th iteration.
Since I needed this fade with a background that is not pure black, but has other content, I created a luminosity mask on a new canvas (contentcanvas) that I then applied to the background. Here is an example showcasing the full solution:
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
let start;
let lastTime;
let stepMod = 0;
function draw() {
canvas.bgcanvas = document.createElement('canvas');
canvas.bgcanvas.width = canvas.width;
canvas.bgcanvas.height = canvas.height;
bgctx = canvas.bgcanvas.getContext('2d');
canvas.contentcanvas = document.createElement('canvas');
canvas.contentcanvas.width = canvas.width;
canvas.contentcanvas.height = canvas.height;
canvas.contentctx = canvas.contentcanvas.getContext('2d');
canvas.tmpcanvas = document.createElement('canvas');
canvas.tmpcanvas.width = canvas.width;
canvas.tmpcanvas.height = canvas.height;
canvas.tmpctx = canvas.tmpcanvas.getContext('2d');
bgctx.fillStyle = '#000';
bgctx.fillRect(0, 0, canvas.width, canvas.height);
bgctx.font = "30px serif";
bgctx.fillStyle = '#ca3';
bgctx.fillText("Hey!", 10, canvas.height/2+10);
}
function step(timestamp) {
if (start === undefined)
start = timestamp;
lastTime = timestamp;
const elapsed = timestamp - lastTime;
lastTime = timestamp;
if (stepMod == 1) {
canvas.contentctx.globalCompositeOperation = "difference";
canvas.contentctx.fillStyle = '#010101';
canvas.contentctx.fillRect(0, 0, canvas.width, canvas.height);
canvas.contentctx.globalCompositeOperation = "source-over";
stepMod = 0;
}
else {
stepMod += 1;
}
var x = (timestamp - start)*0.01 % 200;
var y = 0;
canvas.contentctx.beginPath();
canvas.contentctx.fillStyle = '#fff';
canvas.contentctx.arc(x, canvas.height/2-y, 7, 0, Math.PI * 2, true);
canvas.contentctx.closePath();
canvas.contentctx.fill();
canvas.tmpctx.clearRect(0, 0, canvas.width, canvas.height);
canvas.tmpctx.drawImage(canvas.contentcanvas, 0, 0);
canvas.tmpctx.globalCompositeOperation = "multiply";
canvas.tmpctx.fillStyle = '#77f';
canvas.tmpctx.fillRect(0, 0, canvas.width, canvas.height);
canvas.tmpctx.globalCompositeOperation = "source-over";
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#fff';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.globalCompositeOperation = "difference";
ctx.drawImage(canvas.contentcanvas, 0, 0);
ctx.globalCompositeOperation = "multiply";
ctx.drawImage(canvas.bgcanvas, 0, 0);
ctx.globalCompositeOperation = "lighter";
ctx.drawImage(canvas.tmpcanvas, 0, 0);
ctx.globalCompositeOperation = "source-over";
window.requestAnimationFrame(step);
}
function resizeCanvas() {
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
lines = [];
draw();
ctx.drawImage(canvas.bgcanvas, 0, 0);
}
window.addEventListener('resize', resizeCanvas, false);
resizeCanvas();
window.requestAnimationFrame(step);
* { margin:0; padding:0; } /* to remove the top and left whitespace */
body {
width:100%; height:100%;
}
canvas {
display:block;
}
<canvas id="canvas"></canvas>

Create pattern with canvas

I would like to create a pettern with canvas. The Picture which should be used should also be gernerated first. I already did something like this with this code:
document.addEventListener('DOMContentLoaded', function () {
async function draw() {
var canvas = document.getElementById('canvas1')
var ctx = canvas.getContext("2d");
var canvas = ctx.createImageData(500, 300);
ctx.fillStyle = "#7289DA";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Select the color of the stroke
ctx.strokeStyle = '#74037b';
// Draw a rectangle with the dimensions of the entire canvas
ctx.strokeRect(0, 0, canvas.width, canvas.height);
ctx.font = 'bold 70px sans-serif';
// Select the style that will be used to fill the text in
ctx.save();
ctx.rotate(1.7*Math.PI);
ctx.fillStyle = '#23272A';
ctx.fillText('Text', -70, 300);
ctx.restore();
// Actually fill the text with a solid color
}
draw();
});
<canvas id="canvas" width="1500" height="900">Beispiel für eine Kachelung eines Musters in Canvas.</canvas>
Now I want to create some kind of grid with it, it should look like this
How can I do that?
The best way would be using two for loops to go over the x and y values! You can surround the part that draws text with these loops and use the changing x and y values instead of hard-coded ones.
async function draw() {
var canvas = document.getElementById('canvas1')
var ctx = canvas.getContext("2d");
var canvas = ctx.createImageData(500, 300);
ctx.fillStyle = "#7289DA";
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Select the color of the stroke
ctx.strokeStyle = '#74037b';
// Draw a rectangle with the dimensions of the entire canvas
ctx.strokeRect(0, 0, canvas.width, canvas.height);
ctx.font = 'bold 70px sans-serif';
ctx.fillStyle = '#23272A';
// Select the style that will be used to fill the text in
for (var x = 0; x < canvas.width; x += 100 ) { // 100 is the width
for (var y = 0; y < canvas.height; y += 70) { // 70 is the height
ctx.save();
ctx.translate(x, y); // offset the text
ctx.rotate(1.7*Math.PI);
ctx.fillText('Text', -70, 300);
ctx.restore();
// Actually fill the text with a solid color
}
}
}
The reason ctx.translate(x, y) is used instead of ctx.fillText('Text', x - 70, y + 300) is because using fillText would move the grid at an angle instead of just rotating the letters.

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

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>

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);
}
}