Multiple text shadows in 2d canvas - javascript

In CSS, I can chain multiple definitions in the text-shadow property to achieve effects such as glow:
.green-glow {
padding:20px 40px 40px;
font-family:'Georgia';
font-size:70px;
background-color:#000;
color: #fff;
text-shadow: 0 0 10px #fff, 0 0 20px #fff, 0 0 30px #fff, 0 0 40px #0f0, 0 0 70px #0f0, 0 0 80px #0f0, 0 0 100px #0f0, 0 0 150px #0f0;
}
<div class="green-glow">glow</div>
I would like to achieve similar effects when drawing text on a canvas element using CanvasRenderingContext2D.fillText but since shadow definitions are split among multiple properties (shadowBlur, shadowColor, etc.), it is not immediately obvious for me the adequate way to chain multiple shadows.
I can do multiple calls to CanvasRenderingContext2D.fillText, one for each defined shadow as the following snippet shows...
// get context
var ctx = document.querySelector('#test').getContext('2d');
// clear background to black
ctx.fillStyle = '#000'
ctx.fillRect(0, 0, 500, 150);
// common font attributes
ctx.font = "70px Georgia";
ctx.fillStyle = 'white';
// apply multiple shadows
ctx.shadowColor = '#fff';
ctx.shadowBlur = 10;
ctx.fillText('glow', 50, 90);
ctx.shadowBlur = 20;
ctx.fillText('glow', 50, 90);
ctx.shadowBlur = 30;
ctx.fillText('glow', 50, 90);
ctx.shadowColor = '#0f0';
ctx.shadowBlur = 40;
ctx.fillText('glow', 50, 90);
ctx.shadowBlur = 70;
ctx.fillText('glow', 50, 90);
ctx.shadowBlur = 80;
ctx.fillText('glow', 50, 90);
ctx.shadowBlur = 100;
ctx.fillText('glow', 50, 90);
ctx.shadowBlur = 150;
ctx.fillText('glow', 50, 90);
canvas {
width: 500px;
height: 150px;
}
<canvas id="test" width="500" height="150"></canvas>
... and even though it doesn't quite look the same to me, it would be good enough for my purposes. But I am wondering about possible performance issues caused by those multiple calls to fillText and whether there is a more appropriate way to apply multiple text shadows on a canvas.
Thanks!

Related

Drawing a triangle in a canvas

I want to draw some triangles in a canvas procedurally (eventually adding some animation and other effects). I've managed to get some simple shapes into the canvas but fo some reason cannot draw triangles for some reason. I've been using some code from this question but so far it doesn't work.
HTML
There's more, but for simplicity I've left out the parts that aren't used at all. I'm using Bootstrap. That script path is a valid path as well.
function triangles() {
let left = document.getElementById("left")
let ctx = left.getContext("2d");
ctx.fillStyle = "#ff0000";
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.moveTo(100, 0);
ctx.moveTo(0, 100);
ctx.moveTo(0, 0);
ctx.closePath();
ctx.fill();
ctx.strokeStyle = "blue";
ctx.lineWidth = 4;
ctx.stroke();
}
triangles()
canvas {
width: 400px;
height: 400px;
border: 2px solid #A2A2A2;
}
<div class="min-vh-100">
<div class="row align-items-start">
<div class="col-2">
<canvas id="left"></canvas>
</div>
<div class="col-8">
</div>
<div class="col-2">
<canvas id="right"></canvas>
</div>
</div>
</div>
Using this gives me a completely blank canvas. As said previously, I have played around with other ways of drawing to the canvas such as the fillRect function and can draw properly.
Any advice is probably helpful thanks
You're just moving around. To draw shapes, use lineTo.
let left = document.getElementById("left")
let ctx = left.getContext("2d");
ctx.fillStyle = "red";
ctx.strokeStyle = "blue";
ctx.lineWidth = 4;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(100, 0);
ctx.lineTo(0, 100);
ctx.lineTo(0, 0);
ctx.closePath();
ctx.fill();
ctx.stroke();
canvas {
width: 400px;
height: 400px;
border: 1px solid black;
}
<canvas id="left"></canvas>

Is there something I am doing wrong by trying to make a rectangle?

I am trying to make a rectangle by filling it with html/javascript, but for some reason, even though it's only a few lines of code, I can't figure out what I'm doing wrong here
I've already tried in the code using strokeStyle and not using the viewport , I also tried using var canvas = document.getElementById("myCanvas"); like this, with double quotes
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext("2d");
ctx.fillStyle = "rgb(255, 255, 255)";
ctx.fillRect(0, 0, 40, 40);
<canvas id="myCanvas" style="border: 1px solid black; height: 50px; width: 100vw; max-width: 100%;"></canvas>
I truly don't know anymore what I'm doing wrong and I just want to make a rectangle
You're drawing a white rectangle on a white canvas.
Change ctx.fillStyle = "rgb(255, 255, 255)"; to be a color that shows up on a white background:
var canvas = document.getElementById('myCanvas');
var ctx = canvas.getContext("2d");
ctx.fillStyle = "rgb(255, 0, 0)";
ctx.fillRect(0, 0, 40, 40);
<canvas id="myCanvas" style="border: 1px solid black; height: 50px; width: 100vw; max-width: 100%;"></canvas>

Can't change color from javascript

I can't get to change the color of the stroke when mouseovering the button. I've tried solving it by myself bu i can't.
var canvas = document.getElementById("canvas1");
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(17, 7);
ctx.bezierCurveTo(13, -8, 26, 21, 12, 26);
ctx.bezierCurveTo(-2, 31, 59, 64, 33, 18);
ctx.lineWidth = 8;
ctx.strokeStyle = "#3d7eb8";
if (document.getElementById("one").style.backgroundColor == "#3d7eb8"){
ctx.strokeStyle = "#fffced";
}
ctx.stroke();
function button1hover (){
document.getElementById("one").style = "background-color: #3d7eb8";
}
function button1unhover (){
document.getElementById("one").style = "background-color: #fffced";
}
<button onmouseout="button1unhover()" onmouseover="button1hover()" id="one" class="button column center">
<canvas height="50px" width="50px" id="canvas1"></canvas>
<p>Inici</p>
</button>
This really is no job vor JS, this can all be accomplished with CSS and a tiny inline SVG for the curve.
#one {
background-color: #fffced;
}
#one svg {
width: 50px;
height: 50px;
}
#one:hover {
background-color: #3d7eb8;
}
#one path {
fill: none;
stroke-width: 8px;
stroke: #3d7eb8;
}
#one:hover path {
stroke: #fffced;
}
<button id="one" class="button column center">
<svg><path d="M 17 7 C 13 -8 26 21 12 26 -2 31 59 64 33 18" /></svg>
<p>Inici</p>
</button>
and even the CSS can be nicer if you use less or sass/scss
#one {
background-color: #fffced;
svg {
width: 50px;
height: 50px;
}
path {
fill: none;
stroke-width: 8px;
stroke: #3d7eb8;
}
&:hover {
background-color: #3d7eb8;
path {
stroke: #fffced;
}
}
}
To answer the question why your code does not work: You render the canvas exactly once, at the beginning. To change it, you'd have to re-render it inside of button1hover() and button1unhover() with the resperctive color.
And even then, document.getElementById("one").style.backgroundColor == "#3d7eb8" ain't guaranteed to work. Because, depending on the Browser, .style.backgroundColor may return the color as rgb(...) value.
So better define a variable that stores the state and toggle/check that.
As said before, this is better done in css.
However, if you wish to do it in JS, you could try something like that :
var canvas = document.getElementById("canvas1");
var ctx = canvas.getContext("2d");
ctx.beginPath();
ctx.moveTo(17, 7);
ctx.bezierCurveTo(13, -8, 26, 21, 12, 26);
ctx.bezierCurveTo(-2, 31, 59, 64, 33, 18);
ctx.lineWidth = 8;
ctx.strokeStyle = "#3d7eb8";
ctx.stroke();
function button1hover() {
this.style.background = "#3d7eb8";
ctx.strokeStyle = "#fffced";
ctx.stroke();
}
function button1unhover() {
this.style.background = "#fffced";
ctx.strokeStyle = "#3d7eb8";
ctx.stroke();
}
document.getElementById('one').addEventListener("mouseover",button1hover);
document.getElementById('one').addEventListener("mouseout",button1unhover);
<button id="one" class="button column center">
<canvas height="50px" width="50px" id="canvas1"></canvas>
<p>Inici</p>
</button>
eventListeners are your friends for this kind of things, as for the canvas, I'm not sure there is a better option than redrawing it every time.

How to animate properly in a <canvas>?

So I want to recreate this simple animation effect, which uses a <canavs> :
https://codepen.io/w3devcampus/pen/PpLLKY
HTML:
<html lang="en">
<head>
<meta charset="utf-8">
<title>Draw a monster in a canvas</title>
</head>
<body>
<canvas id="myCanvas" width="200" height="200"></canvas>
</body>
</html>
JavaScript:
function mainLoop() {
ctx.clearRect(0, 0, w, h);
drawMyMonster(xMonster, yMonster);
xMonster += monsterSpeed;
if (((xMonster + 100)> w) || (xMonster < 0)) {
monsterSpeed = -monsterSpeed;
}
}
function drawMyMonster(x, y) {
ctx.save();
ctx.translate(x, y);
ctx.strokeRect(0, 0, 100, 100);
ctx.fillRect(20, 20, 10, 10);
ctx.fillRect(65, 20, 10, 10);
ctx.strokeRect(45, 40, 10, 40);
ctx.strokeRect(35, 84, 30, 10);
ctx.fillRect(38, 84, 10, 10);
ctx.fillRect(52, 84, 10, 10);
ctx.restore();
}
So, I did recreate it, but it wouldn't behave normally. Instead of displaying one frame at a time, it stacks all the frames up each other in the canvas. Here's my version:
https://codepen.io/Undefined_Variable/pen/LraLJQ
HTML:
<html>
<head>
<title>CanvasThingy</title>
</head>
<body>
<canvas id="CanvasForMonster">
No support for you scrub!
</canvas>
</body>
</html>
JavaScript:
var myCanvas, context, w, h;
var xMonster = 60;
var yMonster = 30;
var monsterSpeed = 1;
window.onload = function initial(){
myCanvas = document.body.querySelector('#CanvasForMonster');
w = myCanvas.style.width;
h = myCanvas.style.height;
context = myCanvas.getContext('2d');
ClearCanvas();
}
function ClearCanvas(){
context.clearRect(0, 0, w, h);
DrawFMLface(xMonster, yMonster);
xMonster += monsterSpeed;
if(((xMonster + 175) > 299) || (xMonster < 0) ){
monsterSpeed = -monsterSpeed;
}
requestAnimationFrame(ClearCanvas);
}
function DrawFMLface(x, y){
context.save();
context.translate(x, y);
context.strokeRect(0, 4, 175, 80);
context.strokeStyle = 'black';
context.fillRect(40, 10, 20, 20);
context.strokeStyle = 'black';
context.fillRect(110, 10, 20, 20);
context.strokeStyle = 'black';
context.strokeRect(85, 40, 1, 15);
context.strokeStyle = 'black';
context.strokeRect(42, 70, 90, 0);
context.strokeStyle = 'black';
context.restore();
}
Please tell me how to do this properly without jQuery AT ALL, please. I'm still learning and I don't want any jQuery/Angular/React etc. in my code, please!
Your problem is that the previous faces are not being cleared. The clearing is supposed to be done with this line:
ctx.clearRect(0, 0, w, h);
The problem is that w and h are not set correctly when this is run, so not enough of the canvas is cleared.
This is where you set w and h:
w = myCanvas.style.width;
h = myCanvas.style.height;
This will only work if the width and height were set with CSS, e.g. with an HTML attribute style="width: 200px; height: 200px;", and if you called parseInt to convert the CSS style strings into numbers. But in fact the canvas element’s width and height were set with width and height HTML attributes directly. So you should set w and h with HTMLCanvasElement’s width and height properties:
w = canvas.width;
h = canvas.height;
The original Codepen sets them this way.
Just in case you were looking for a possibly easier way to do this, you can actually avoid JavaScript entirely when creating a simple animation like this by using CSS animations:
#CanvasForMonster {
position: relative;
width: 200px;
height: 200px;
border: 1px solid #000;
}
#Monster {
position: absolute;
top: 25%;
width: 50%;
height: 50%;
animation: bounce 2s alternate linear infinite;
}
#keyframes bounce {
from { left: 0; }
to { left: 50%; }
}
#Monster div {
position: absolute;
}
#Monster .stroke {
border: 1px solid #000;
}
#Monster .fill {
background-color: #000;
}
#Monster .head {
left: 0;
top: 0;
width: 100%;
height: 100%;
}
#Monster .eye {
width: 10%;
height: 10%;
}
#Monster .eye-left {
left: 25%;
top: 25%;
}
#Monster .eye-right {
left: 65%;
top: 25%;
}
#Monster .nose {
left: 50%;
top: 50%;
width: 1px;
height: 10%;
}
#Monster .mouth {
left: 25%;
top: 75%;
width: 50%;
height: 0;
}
<div id="CanvasForMonster">
<div id="Monster">
<div class="stroke head"></div>
<div class="fill eye eye-left"></div>
<div class="fill eye eye-right"></div>
<div class="stroke nose"></div>
<div class="stroke mouth"></div>
</div>
</div>
Everything you have is alright.
The problem is your Globals.
The width of the canvas should be set to:
w = myCanvas.width;
Height set to:
h = myCanvas.height;
You have both set as:
w = myCanvas.style.width;
h = myCanvas.style.height;
Which will not work.
Here is the fork (Demo).
I would highly recommend using JQuery when working with canvas.
When using canvas it should be noted that you want too reference canvas.height, and canvas.width as much as possible rather then using a global that points at the height, and width of the css style.

how to make a shadow in HTML canvas

I need to draw a canvas rect with shadow which has shadows on four sides of the rect, similar to a div has style as "box-shadow":"0px 0px 5px 5px"
You can use context.shadowColor plus context.shadowBlur to create the box-shadow effect.
Canvas's blur is very light so you must often overdraw the blurs to make them prominent.
Here's my version of your box-shadow: 0 0 5px 5px:
Example annotated code:
shadowRect(10,10,105,45,5,'red');
function shadowRect(x,y,w,h,repeats,color){
// set stroke & shadow to the same color
ctx.strokeStyle=color;
ctx.shadowColor=color;
// set initial blur of 3px
ctx.shadowBlur=3;
// repeatedly overdraw the blur to make it prominent
for(var i=0;i<repeats;i++){
// increase the size of blur
ctx.shadowBlur+=0.25;
// stroke the rect (which also draws its shadow)
ctx.strokeRect(x,y,w,h);
}
// cancel shadowing by making the shadowColor transparent
ctx.shadowColor='rgba(0,0,0,0)';
// restroke the interior of the rect for a more solid colored center
ctx.lineWidth=2;
ctx.strokeRect(x+2,y+2,w-4,h-4);
}
var canvas=document.getElementById("canvas");
var ctx=canvas.getContext("2d");
var cw=canvas.width;
var ch=canvas.height;
shadowRect(10,10,105,45,5,'red');
function shadowRect(x,y,w,h,repeats,color){
// set stroke & shadow to the same color
ctx.strokeStyle=color;
ctx.shadowColor=color;
// set initial blur of 3px
ctx.shadowBlur=3;
// repeatedly overdraw the blur to make it prominent
for(var i=0;i<repeats;i++){
// increase the size of blur
ctx.shadowBlur+=0.25;
// stroke the rect (which also draws its shadow)
ctx.strokeRect(x,y,w,h);
}
// cancel shadowing by making the shadowColor transparent
ctx.shadowColor='rgba(0,0,0,0)';
// restroke the interior of the rect for a more solid colored center
ctx.lineWidth=2;
ctx.strokeRect(x+2,y+2,w-4,h-4);
}
body{ background-color: ivory; padding:10px;}
<h4>box-shadow: 0 0 5px 5px red</h4>
<div style="box-shadow: 0 0 5px 5px red; height: 40px; width:100px;"></div>
<br>
<h4>Canvas "box-shadow" using context shadowing</h4>
<canvas id="canvas" width=200 height=100></canvas>
CanvasRenderingContext2D.shadowColor property in combination with shadowBlur, shadowOffsetX, or shadowOffsetY does just that.
Example:
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const shadowRect = ({
x = 15,
y = 15,
w = 100,
h = 40,
fillStyle = 'tomato',
shadowOffsetX = 0,
shadowOffsetY = 3,
shadowBlur = 15,
shadowColor = "rgba(21, 24, 50, 0.3)"
} = {}) => {
ctx.shadowOffsetX = shadowOffsetX;
ctx.shadowOffsetY = shadowOffsetY;
ctx.shadowBlur = shadowBlur;
ctx.shadowColor = shadowColor;
ctx.fillStyle = fillStyle;
ctx.fillRect(x, y, w, h);
}
shadowRect({ fillStyle: '#C99DFE' });
body {
background-color: #E5E5E5;
padding: 10px;
font-family: sans-serif;
font-size: 16px;
}
.shadowRect {
margin: 15px 0 0 15px;
background-color: #C99DFE;
box-shadow: 0px 3px 15px rgba(21, 24, 50, 0.3);
border-radius: 2px;
width:100px;
height: 40px;
}
<h4>CSS</h4>
<pre>
width: 100px;
height: 40px;
margin: 15px 0 0 15px;
background-color: #C99DFE;
box-shadow: 0px 3px 15px rgba(21, 24, 50, 0.3);
</pre>
<div class="shadowRect"></div>
<br>
<h4>Canvas</h4>
<pre>
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
ctx.shadowOffsetX = 15;
ctx.shadowOffsetY = 15;
ctx.shadowBlur = 15;
ctx.shadowColor = 'rgba(21, 24, 50, 0.3)';
ctx.fillStyle = '#C99DFE';
ctx.fillRect(15, 15, 100, 40);
</pre>
<canvas id="canvas" width=200 height=100></canvas>

Categories