Unable to get clearRect to work with rotation and arc - javascript

I am trying to animate a small dot along a curved path using a HTML5 Canvas. The animation i have working but i cannot get the clearRect to work so each frame is just stacking on top of the next.
Here is the drawing code, can2 and ctx2 are global variable set in the .ready function of the page.
function drawTempStep(){
//Clear the Canvas
ctx2.clearRect(0, 0, can2.width, can2.height);
ctx2.save();
//Check we're centered
ctx2.moveTo(0, 0);
//Rotate Canvas so we are drawing strait
ctx2.rotate(getRadians(canvasRotation));
//Begin new Path
//Create reflection for the dot
//First create gradiated fill style
var grd = ctx2.createRadialGradient(0, weatherDivSize.width / 2 * .70, 0, 0, weatherDivSize.width / 2 * .70, weatherDivSize.width*.2);
grd.addColorStop(0, "rgba(176,0,0,0.2)");
grd.addColorStop(1, "rgba(0,0,0,0)");
//Now beging the path
ctx2.beginPath();
//Draw the Arc
ctx2.arc(0, weatherDivSize.width / 2 * .70, weatherDivSize.width*.2, 0, 2 * Math.PI, false);
ctx2.closePath();
//Fille and stroke it
ctx2.fillStyle = grd;
ctx2.fill();
ctx.stroke();
//Now lets draw the smaller inner circle
ctx2.beginPath();
ctx2.arc(0, weatherDivSize.width / 2 * .70, weatherDivSize.width*.009, 0, 2 * Math.PI, false);
ctx2.closePath();
ctx2.fillStyle = '#B00000';
ctx2.fill();
ctx.stroke();
//Return the canvas rotation to it's starting point
ctx2.rotate(-getRadians(canvasRotation));
//All done so restore the context to it's previous state
ctx2.restore();
}
Here is a link to the the full project on CodePen so you can see what's going on.
CodePen Link
Thanks
Gareth

Related

Html canvas zoom to specific region

I have Html canvas code that draws the following graph. Is there an easy way of zooming the area that is outlined with red rectangle without rewriting the code? This area is always top right quarter of the graph and I also would like to leave some space for the axis.
So, I found the solution:
Draw two times bigger original canvas
Retrieve and put the region image on new canvas:
var imgData = ctx.getImageData(canvas.width/2 - 20, 0, canvas.width/2 + 20, canvas.width/2 + 20);
ctx.clearRect(0, 0, canvas.width, canvas.height);
canvas.width /= 2
canvas.height /= 2
canvas.height += 20
ctx.putImageData(imgData, 0, 0);

Using Canvas via JavaScript, how can I change the radius of a circle after a button is clicked?

I'm trying to make it so that when I clicked the button to change the radius size, it would change all the circle's radius size into a random size between 5-10.
I've already tried to draw 2 circles (1 black, 1 blue) and call then tried to change the radius to a new random radius size between 5-10. After adding my function, I cannot see the 2 circles I have drawn. Any feedback is greatly appreciated.
<canvas width="300" height="300" id="myCanvas" style="border: 1px solid black"></canvas>
<button id="changeRadius">Change Size</button>
<script>
var number = Math.min(Math.max(paraseInt(number), 5), 10);
let canvas = document.getElementById("myCanvas");
let ctx = canvas.getContext("2d");
ctx.translate(100,120);
ctx.beginPath();
ctx.arc(0, 0, 10, 0, 2 * Math.PI);
ctx.fillStyle = "black";
ctx.fill();
ctx.translate(140,120);
ctx.beginPath();
ctx.arc(0, 0, 10, 0, 2 * Math.PI);
ctx.fillStyle = "blue";
ctx.fill();
document.getElementById("changeRadius").onclick = function() {
ctx.clearRect
ctx.translate(100,120);
ctx.beginPath();
ctx.arc(0, 0, "number", 0, 2 * Math.PI);
ctx.fillStyle = "black";
ctx.fill();
ctx.translate(140,120);
ctx.beginPath();
ctx.arc(0, 0, "number", 0, 2 * Math.PI);
ctx.fillStyle = "blue";
ctx.fill();
if (event.keycode == s){
ctx.translate(100,120);
ctx.beginPath();
ctx.arc(0, 0, 5, 0, 2 * Math.PI);
ctx.fillStyle = "black";
ctx.fill();
ctx.translate(140,120);
ctx.beginPath();
ctx.arc(0, 0, 5, 0, 2 * Math.PI);
ctx.fillStyle = "blue";
ctx.fill();
}else if (event.keycode == b) {
ctx.translate(100,120);
ctx.beginPath();
ctx.arc(0, 0, 10, 0, 2 * Math.PI);
ctx.fillStyle = "black";
ctx.fill();
ctx.translate(140,120);
ctx.beginPath();
ctx.arc(0, 0, 10, 0, 2 * Math.PI);
ctx.fillStyle = "blue";
ctx.fill();
}
}
</script>
There are many simple mistakes in the code.
1.
Where you call clearRect, you have:
ctx.clearRect
but it needs to be:
ctx.clearRect(x, y, width, height);
where x, y, width and height define the rectangle that you want to clear. To clear the whole canvas, use:
ctx.clearRect(0, 0, canvas.width, canvas.height);
2.
You have misspelled parseInt as paraseInt.
3.
Where you have put "number" as the radius in the calls to ctx.arc... I don't even know what you are trying to achieve. It looks like a placeholder? Anyway, it needs to be an actual number, not just the string "number".
Perhaps you intended to use the variable number that you create at the top and never use?
ctx.arc(0, 0, "number", 0, 2 * Math.PI);
becomes
ctx.arc(0, 0, number, 0, 2 * Math.PI);
(notice no " characters)
4.
You never actually update the value of number, so even the above won't do anything.
Michael, your question has been on SO for some time but hopefully this answer might still be useful to you as well as others still learning JavaScript. Once I addressed basic syntax issues listed by Mat, the missing circles became visible. At that point I discovered three things.
images drawn on your canvas may not appear where you expect them.
a variable declared and initialised as number in your first
statement does not generate a valid number let alone a random number.
your code has if and if else statements that will not do anything
without an event handler.
#1
Your code uses ctx.arc() to draw a basic circle and ctx.translate() to move it into position on the canvas. With ctx.translate() two parameters x and y determine how far to move the circle; x is the distance to move in the horizontal direction; y is the distance to move in the vertical direction.
The problem is that you no longer know the position coordinates on the canvas after the move. For this reason the origin needs to be reset before proceeding to the next ctx.translate() operation.
To reset the origin use CanvasRenderingContext2D.setTransform()e.g.
ctx.setTransform(1, 0, 0, 1, 0, 0);
from Mozilla MDN
The CanvasRenderingContext2D.setTransform() method of the Canvas 2D
API resets (overrides) the current transformation to the identity
matrix, and then invokes a transformation described by the arguments
of this method. This lets you scale, rotate, translate (move), and
skew the context.
This API is designed for animated drawing with a variety of methods for transforming 2D shapes dynamically. For the purposes of your code it is enough to understand that by clearing the identity matrix you restore 0,0 (the canvas origin) as the reference for x and y values used in any drawing operation that follows. Before you use ctx.translate() reset the origin. Watch the difference this will make when the code launches.
effect of origin reset at launch
without / with
Whenever a new image is drawn the entire canvas must first be cleared using ctx.clearRect()and it is important to reset the origin before doing so. Click the 'Change Size' button in each example to see that the entire screen is cleared with an origin reset. Without a reset the origin of the cleared rectangle will align with the centre of the last circle drawn i.e. the cleared rectangle eats into the lower right quadrant of the circle.
effect of origin reset on clearing the canvas
without / with
#2
The second problem can be demonstrated by inserting alert(number) following the first instruction. When the code runs, alert reports a NaN message (i.e. not a number). Also, random values lie within a range from 0.0 to 1.0 but for your purposes can be scaled to a range from 5.0 to 10.0 (for more see here and here).
The code below shows getNewRadius(), a function that generates a random number whenever the 'Change Size' button is clicked. Before the function is called constants and variables are declared and initialised. Every time the function is called, i.e. with every mouse click, it returns a new random value of radius which is then passed as an argument to the next drawing function.
For clarity I renamed your variable number to the more self-explanatory radius and replaced magic numbers in your code with some aptly named constant or var.
#3
Statements within the scope of your if and else if tests will never be executed. Testable values are potentially created at runtime by a keyboard event (i.e. event.keycode). But in your code no handler has been set up to listen for such an event. Moreoever, Mozilla MDN advises that keyCode is obsolete and makes the following recommendation
MDN Warning: This attribute is deprecated; you should use
KeyboardEvent.key instead, if available.
For a more comprehensive summary of keyboard event options see here.
The code below includes a basic function called setMaxOrMinRadius(e) that allows maximum or minimum radius values to be selected by typing on a keyboard. In keeping with your initial code, typing s will select minRadius, typing b will select maxRadius. This function will only be recognised if a key is pressed and the event listener has been initialised. If the event listener detects a keystroke it will now be able to switch to the function and recognise which key has been pressed.
Acknowledgement
This answer is a collaborative effort between fellow retiree and former colleague Bob Douglas and myself. We have been learning JavaScript together for two months. We welcome suggestions for improvement.
Code
<canvas width="300" height="300" id="myCanvas" style="border: 1px solid black"></canvas>
<button id="changeRadius">Change Size</button>
<script>
const maxRadius = 10;
const minRadius = 5;
var range = maxRadius - minRadius;
var radius = maxRadius;
let canvas = document.getElementById("myCanvas");
let ctx = canvas.getContext("2d");
document.addEventListener('keydown', setMaxOrMinRadius);
drawCircles(maxRadius);
document.getElementById("changeRadius").onclick = function() {
clearCanvas();
radius = getNewRadius();
drawCircles(radius);
}
function setMaxOrMinRadius(e) {
clearCanvas();
switch (e.which) {
case 83: // type 's'
drawCircles(minRadius);
break;
case 66: // type 'b'
drawCircles(maxRadius);
break;
}
}
function getNewRadius() {
var radius = (Math.random() * minRadius) + range;
return radius;
}
function drawCircles(radius) {
drawBlueCircle(radius);
drawBlackCircle(radius);
}
function clearCanvas(){
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset origin
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
function drawBlackCircle (radius) {
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset origin
ctx.translate(100,120);
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
ctx.fillStyle = "black";
ctx.fill();
}
function drawBlueCircle (radius) {
ctx.setTransform(1, 0, 0, 1, 0, 0); // reset origin
ctx.translate(140,120);
ctx.beginPath();
ctx.arc(0, 0, radius, 0, 2 * Math.PI);
ctx.fillStyle = "blue";
ctx.fill();
}

Create three circle mask with canvas

I need to create a circle mask with canvas, I'm trying to do that, but I can't get it.
I knew to do that with Flash, but I prefef to do without that technology:
May anybody help me with this? I don't know too much about javascript
What I need is create three circles with different sizes on a picture (Canvas with background color).
I know that you are not here to do the job of others but however much I tried I hace not gotten...
You can add as many circles as you need, you must only indicate the position and the desired radius:
JavaScript:
var context = document.getElementById('canvas').getContext('2d');
// Color of the "mask"
context.fillStyle = '#000';
// Rectangle with the proportions of the image (600x400)
context.fillRect(0,0,600,400);
/**
* #param x Specifies the x-coordinate
* #param y Specifies the y-coordinate
* #param radius Specifies radius of the circle
*/
var clearCircle = function(x, y, radius){
context.save();
context.globalCompositeOperation = 'destination-out';
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
context.fill();
context.restore();
};
// First circle
clearCircle(155, 190, 50);
// Second circle
clearCircle(300, 190, 70);
// Third circle
clearCircle(440, 200, 60);
HTML:
<canvas id="canvas" width="600" height="400" />
CSS:
canvas{
background: url("http://cdn2.epictimes.com/derrickblair/wp-content/uploads/sites/9/2015/01/happy-people.jpg") no-repeat;
}
You can see this in action here: http://jsfiddle.net/tomloprod/szau09x6/
Related links:
You should read more about canvas here: http://www.w3schools.com/html/html5_canvas.asp

Multiple light sources on canvas

I want to place a number of light sources on a background for a game I'm making, which works great with one light source as shown below:
This is achieved by placing a .png image above everything else that becomes more transperant towards the center, like this:
Works great for one light source, but I need another approach where I can add more and move the light sources around.
I have considered drawing a similar "shadow layer" pixel by pixel for each frame, and calculate the transparency depending of the distance to each light source. However, that would probably be very slow and I'm sure there are way better solutions to this problem.
The images are just examples and each frame will have considerably more content to move around and update using requestAnimationFrame.
Is there a light weight and simple way to achieve this? Thanks in advance!
Edit
With the help of ViliusL, I came up with this masking solution:
http://jsfiddle.net/CuC5w/1/
// Create canvas
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width = 300;
canvas.height = 300;
document.body.appendChild(canvas);
// Draw background
var img=document.getElementById("cat");
ctx.drawImage(img,0,0);
// Create shadow canvas
var shadowCanvas = document.createElement('canvas');
var shadowCtx = shadowCanvas.getContext('2d');
shadowCanvas.width = canvas.width;
shadowCanvas.height = canvas.height;
document.body.appendChild(shadowCanvas);
// Make it black
shadowCtx.fillStyle= '#000';
shadowCtx.fillRect(0,0,canvas.width,canvas.height);
// Turn canvas into mask
shadowCtx.globalCompositeOperation = "destination-out";
// RadialGradient as light source #1
gradient = shadowCtx.createRadialGradient(80, 150, 0, 80, 150, 50);
gradient.addColorStop(0, "rgba(255, 255, 255, 1.0)");
gradient.addColorStop(1, "rgba(255, 255, 255, .1)");
shadowCtx.fillStyle = gradient;
shadowCtx.fillRect(0, 0, canvas.width, canvas.height);
// RadialGradient as light source #2
gradient = shadowCtx.createRadialGradient(220, 150, 0, 220, 150, 50);
gradient.addColorStop(0, "rgba(255, 255, 255, 1.0)");
gradient.addColorStop(1, "rgba(255, 255, 255, .1)");
shadowCtx.fillStyle = gradient;
shadowCtx.fillRect(0, 0, canvas.width, canvas.height);
Another way to play with light is to use the globalCompositeOperation mode 'ligther' to ligthen things, and just use globalAlpha to darken things.
First here's an image, with a cartoon lightening on the left, and a more realistic lightening on the right, but you'd rather watch the fiddle, since it's animated :
http://jsfiddle.net/gamealchemist/ABfVj/
So how i did things :
To darken :
- Choose a darkening color( most likely black, but you can choose a red or another color to teint the result).
- choose an opacity ( 0.3 seems a good start value ).
- fillRect the area you want to darken.
function darken(x, y, w, h, darkenColor, amount) {
ctx.fillStyle = darkenColor;
ctx.globalAlpha = amount;
ctx.fillRect(x, y, w, h);
ctx.globalAlpha = 1;
}
To lighten :
- Choose a lightening color. Beware that this color's r,g,b will be added to the previous point's r,g,b : if you use a high value your color will get burnt.
- change the globalCompositeOperation to 'lighter'
- you might change opacity also, to have more control over the lightening.
- fillRect or arc the area you want to lighten.
If you draw several circles while in lighter mode, the results will add up, so you can choose a quite low value and draw several circles.
function ligthen(x, y, radius, color) {
ctx.save();
var rnd = 0.03 * Math.sin(1.1 * Date.now() / 1000);
radius = radius * (1 + rnd);
ctx.globalCompositeOperation = 'lighter';
ctx.fillStyle = '#0B0B00';
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * π);
ctx.fill();
ctx.fillStyle = color;
ctx.beginPath();
ctx.arc(x, y, radius * 0.90+rnd, 0, 2 * π);
ctx.fill();
ctx.beginPath();
ctx.arc(x, y, radius * 0.4+rnd, 0, 2 * π);
ctx.fill();
ctx.restore();
}
Notice that i added a sinusoidal variation to make the light more living.
Ligthen : another way :
You can also, while still using the 'ligther' mode, use a gradient to have a smoother effect (first one is more cartoon like, unless you draw a lot of circles.).
function ligthenGradient(x, y, radius) {
ctx.save();
ctx.globalCompositeOperation = 'lighter';
var rnd = 0.05 * Math.sin(1.1 * Date.now() / 1000);
radius = radius * (1 + rnd);
var radialGradient = ctx.createRadialGradient(x, y, 0, x, y, radius);
radialGradient.addColorStop(0.0, '#BB9');
radialGradient.addColorStop(0.2 + rnd, '#AA8');
radialGradient.addColorStop(0.7 + rnd, '#330');
radialGradient.addColorStop(0.90, '#110');
radialGradient.addColorStop(1, '#000');
ctx.fillStyle = radialGradient;
ctx.beginPath();
ctx.arc(x, y, radius, 0, 2 * π);
ctx.fill();
ctx.restore();
}
i also added here a sin variation.
Rq : creating a gradient on each draw will create garbage : store the gradient if you use a single gradient, and store them in an array if you want to animate the gradients.
If you are using the same light in several places, have a single gradient built, centered on (0,0), and translate the canvas before drawing always with this single gradient.
Rq 2 : you can use clipping to prevent some parts of the screen to be lightened (if there's an obstacle).
I added the blue circle on my example to show this.
So you might want to ligthen directly your scene with those effects, or create separately a light layer that you darken/lighten as you want before drawImage it on the screen.
There are too many scenari to discuss them here (light animated or not, clipping or not, pre-compute a light layer or not, ...) but as far as speed is concerned, for Safari and iOS safari, the solution using rect/arc draws -either with gradient or a solid fill- will be rocket faster than drawing an image/canvas.
On Chrome it will be quite the opposite : it's faster to draw an image than to draw each geometry when the geometry count raises.
Firefox is rather similar to Chrome for this.
your png should have full transparent corners and not transparent white in middle.
or you can draw this, but not pixel by pixel like here jsfiddle.net/pr9r7/2/
More examples: jsfiddle.net/pr9r7/3/ http://codepen.io/cwolves/pen/prvnb
Here is my Take on it:
A. Don't worry about performance until you have tried it out. The Canvas is pretty darn fast at drawing.
B. Rather than having a image with dark Corners and a Transparent middle. Why don't you try and make it more "IRL" and have the overall world be more Dark and let the light-source illuminate the Area? Highlight a small area, instead of darken everything EXCEPT a small Area.

How can i draw a Square in HTML5 Canvas at run time?

I am working on a HTML5 Project.There is a drawing graphics API to draw Rectangle (fillRectStrokeRect).But how can i draw a SQUARE. I have tried the following way to draw it
CODE
getMouse(e);
x2=mx; y2=my;
var width=endX-startX;
var height=endY-startY;
annCanvasContext.beginPath();
annCanvasContext.lineWidth=borderWidth;
var centerX=width/2;
var centerY=width/2;
var radius=width/2;
annCanvasContext.arc(centerX+5, centerY+5, radius, 0, 2 * Math.PI, false);
annCanvasContext.stroke();
Use fillRect or strokeRect with the width and height being equal.
var x = 0, y = 0,
side = 10;
ctx.fillRect(x, y, side, side);
Demo
As you say in the comments, if you want to fit the largest square in a circle, it's more Math related than about code. I'll trying explaining it to you, but you'll probably find better, more visual explanations elsewhere on the Internet.
Draw the diameter of the circle in a way that it divides your square into two equal parts. Now one part is a right angled triangle, which has two of its sides equal. We know the diameter. Using the Pythogorean theorem, you get this equation:
side^2 + side^2 = diameter^2.
Let's find the side now.
2(side^2) = diameter^2
side^2 = (diameter^2)/2
side = Math.sqrt( (diameter^2)/2 )
Now, to turn this into code.
var ctx = document.getElementById('canvas').getContext('2d'),
radius = 20;
ctx.canvas.addEventListener('click', function (e){
ctx.fillStyle = 'black';
ctx.arc(e.pageX, e.pageY, radius, 0, Math.PI*2, false);
ctx.fill();
ctx.beginPath();
ctx.fillStyle = 'red';
var diameter = radius * 2;
var side = Math.sqrt( (diameter * diameter)/2 );
ctx.fillRect(e.pageX - side/2, e.pageY - side/2, side, side);
ctx.closePath();
}, false);
This would draw a square inside a circle wherever you click on the canvas.
Demo

Categories

Resources