Making analog clock in canvas - javascript

I have to make an analog clock in canvas. Wrote some code and I need help with how to adjust speed of the hands (hours, mins and seconds). When I start the app hands are just spinning around really fast. I am ending up with hands line allover the clock. Is there any way to remove those lines?
I tried adjusting the rotation but that didn't help. I just started learning about canvas and am really not sure how to fix this problem.
window.onload = draw;
function draw() {
let myCanvas = document.getElementById("my-canvas");
if (myCanvas.getContext) {
let ctx = myCanvas.getContext('2d');
var img = new Image();
img.addEventListener('load', function() {
ctx.drawImage(img, 0, 0, 400, 400);
}, false);
img.src = 'image.png';
update(ctx);
} else {
alert("Canvas is not supported.");
}
}
let angle = 0;
function update(ctx) {
ctx.save();
ctx.translate(200, 200);
ctx.rotate((Math.PI / 180) * angle);
ctx.translate(-200, -200);
ctx.beginPath();
ctx.moveTo(200, 200);
ctx.lineTo(200, 150);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(200, 200);
ctx.lineTo(200, 100);
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(200, 150);
ctx.lineTo(200, 30);
ctx.closePath();
ctx.stroke();
ctx.restore();
angle++;
requestAnimationFrame(function() {
update(ctx);
});
}
#my-canvas {
border: #cacaca 1px solid;
}
<canvas id="my-canvas" width="400" height="400">
Your web browser does not support canvas element.
</canvas>

I'm not going to write code for you, since this is homework, but I can walk you through some of the changes I think you need to make.
First off, instead of incrementing your "angle" variable for every iteration, I'd suggest using basic geometry or algebra to figure out where the hand needs to be based on what second, minute, or hour you are wanting to display on a 360 degree circle. You already have some of it, you just need to make it work for time, instead of an ever increasing variable. This will help you reuse the "update" method for each hand.
Next, don't use a recursive method. Use a timer. Since this is a clock, I'd suggest setInterval(), but there's some caveats to this, so look at the link below. Also, I would play around with the timer delay to match the smoothness you want. Do you want the hands to smoothly go around the clock face or do you want them to snap to the next number? You can do some of that in your angle calculations too, but I'd suggest starting at 250ms and see how that looks. If it's not smooth enough, reduce the delay.
How to create an accurate timer in javascript?
Instead of calling "update" directly in the "draw" method, you'll instead set up the timer/interval.
Having this as a recursive method prevents other events from happening in your code, so if you have to add other features, they won't work without some complicated logic. Setting up the timer/interval will allow your code to act in a more multi-threaded way, but not true parallel threads. This gets into somewhat advanced topics, so I'll just stop there.
Third, you will want to add parameters to the "update" method for which hand you are updating and the time. This is because you will be calling it from a new method you create that calls "update" for each hand. This new method is the one that is fed into setInterval. You can use these parameters to determine the length and/or width of your clock hands. Doing it this way avoids you having to create duplicate methods for each hand.
Fourth, now that you have the correct code in the question, you need to add back in the line that clears the canvas, as Mike 'Pomax' Kamermans mentions in the comments. This comes after the "cts.save()" line in your "update" method. Also, your clock is apparently a different size than the original code, so your have to clear a larger area.
ctx.clearRect(0, 0, 400, 400);

Related

Canvas Context.drawImage() is leaving behind duplicates of Sprite on UpdatePosition()

I've been experimenting with a basic game loop with HTML's Canvas element. Numerous tutorials online don't go into enough detail with the concepts of rendering and canvas.ctx (context).
What I'm trying to do is something very simple: Render an image on a canvas element and, on keydown, update its position and render it at the new location, making it move across the screen. Basically, what every video game does with its sprites.
I've been told through these tutorials that ctx.drawImage(image, x, y, ...) will work for this. However, what ends up happening in my version is essentially what happens when you win a game of solitaire on windows. It repeats the sprite's image as if it's creating a brand new sprite each time the game loops. The sprite itself doesn't move, a new sprite seems to be generated to the left/right/etc of the original one. I understand that I'm calling ctx.drawImage(...) every time I'm iterating through the game loop. However, this didn't happen when I used ctx.clearRect(...). It worked precisely how I expected it to. I'm not exactly sure why creating a rectangle with ctx works while creating an image doesn't.
My question is: Is there a way to simply update the position of the sprite without creating a brand new version of it every single loop?
Here's my relevant code:
let lastRender = 0; // For the general loop
let image = new Image();
image.src = "/img/image.png";
let state = {
pressedKeys: {
// left, right, up, down: false
},
position: {
x: canvas.width / 2,
y: canvas.width / 2
},
speed: 20
}
let pepsi = new Sprite({
img: image,
width: 100,
height: 100
)};
function Sprite (options) {
this.img = options.img;
this.width = options.width;
this.height = options.height;
this.render = function(){
ctx.drawImage(
this.img,
state.position.x,
state.position.y
)
}
}
function updatePosition(progress) {
//pressedKeys is just an object that relates WASD to the key codes
// and their respective directions, it's ignorable
if (state.pressedKeys.left) {
state.position.x -= state.speed;
}
if (state.pressedKeys.right) {
state.position.x += state.speed;
}
if (state.pressedKeys.up) {
state.position.y -= state.speed;
}
if (state.pressedKeys.down) {
state.position.y += state.speed;
}
}
function draw() {
pepsi.render();
}
function loop(timestamp) {
let progress = timestamp - lastRender;
update(progress) // <-- Updates position, doesn't touch draw()
draw(); // <-- Runs pepsi.render(); each loop
lastRender = timestamp;
window.requestAnimationFrame(loop);
}
window.requestAnimationFrame(loop); // for the general loop
If you have any qualms with the way this project is set up (for example, using the state.position for each Sprite), then I'd be glad to hear them in addition to the solution to my problem. Not in isolation. I got most of this code from contextless, non-specific online tutorials, but I understand most of it, save for the rendering.
Also, if you've seen this kind of question before and are on the fence about saying "Possible duplicate of {Borderline Tangentially-Related Post from Four Years Ago}", then here's some advice: Just answer the question again. It literally does nothing negative to you.
The solitaire smearing effect that you are getting, comes from the fact each frame is being drawn over the top of the last one. The canvas doesn't get cleared automatically between frames.
You mentioned that you have used clearRect, the use of clearRect is to clear all the pixels in the specified rectangle.
So if you put ctx.clearRect(0, 0, canvas.width, canvas.height) in the draw function before pepsi.render(), that should clear the canvas before drawing the next frame.

ctx.clearRect canvas sprite

I am wondering how I could alter my Javascript to only clear the falling sprites, and not the entire canvas (as it does currently).
I hope to place multiple other (animated) sprites on the canvas, which do not appear with the way my function animate is structured.
Is there a way so that if there was another image/sprite was on the canvas, it would not be affected by the function animate.
I'm thinking that this line needs to change:
ctx.clearRect(0, 0, canvas.width, canvas.height);
Though I have no idea what parameters I would need to place inside.
The falling sprites draw at a size of 60x60, but as they fall downwards this is where I am a bit stuck with clearing the only the sprite path.
Any help would be appreciated :)
var canvas = document.getElementById("canvas"),
ctx = canvas.getContext("2d");
canvas.width = 1408;
canvas.height = 640;
canvasWidth = canvas.width;
canvasHeight = canvas.height;
var orangeEnemy = new Image();
orangeEnemy.src = "http://www.catholicsun.org/wp-content/uploads/2016/09/cropped-sun-favicon-512x512-270x270.png";
var yellowEnemy = new Image();
yellowEnemy.src = "http://www.clker.com/cliparts/o/S/R/S/h/9/transparent-red-circle-hi.png";
var srcX;
var srcY;
var enemySpeed = 2.75;
var images = [orangeEnemy, yellowEnemy];
var spawnLineY=-50;
var spawnRate=2500;
var spawnRateOfDescent=1.50;
var lastSpawn=-1;
var objects=[];
var startTime=Date.now();
animate();
function spawnRandomObject() {
var object = {
x: Math.random() * (canvas.width - 15),
y: spawnLineY,
image: images[Math.floor(Math.random() * images.length)]
}
objects.push(object);
}
function animate(){
var time=Date.now();
if(time>(lastSpawn+spawnRate)){
lastSpawn=time;
spawnRandomObject();
}
requestAnimationFrame(animate);
ctx.clearRect(0, 0, canvas.width, canvas.height);
// move each object down the canvas
for(var i=0;i<objects.length;i++){
var object=objects[i];
object.y += enemySpeed;
ctx.drawImage(object.image, object.x, object.y, 60, 60);
}
}
<html>
<canvas id="canvas" style="border:3px solid"></canvas>
</html>
The easiest and quickest way would be to overlay another canvas, specifically for your sprites, atop your current canvas (requires a bit of CSS). Put all your sprites in one, everything else in the other. The clearRect() in your animate() function will then only apply to your sprite canvas, and not the other.
Otherwise, you will have to keep track of the positions of the sprites, and clear each programatically with 60x60 rectangles using clearRect(offsetX, offsetY, 60, 60).
P.S. excuse the non-formatted answer... still figuring SO out
Clear once for performance.
You are much better off clearing the whole canvas and redrawing the sprites. Using the previous position, checking for overlap and then clearing each sprite in turn, making sure you don't clear an existing sprite will take many more CPU cycles than clearing the screen once.
The clear screen function is very fast and is done in hardware, the following is the results of a performance test on Firefox (currently the quickest renderer) of clearing 65K pixels using just one call for whole area then 4 calls each a quarter, then 16 calls each clearing a 16th. (µs is 1/1,000,000th second)
Each test clears 256*256 pixels Each sample is 100 tests
'Clear 1/1' Mean time: 213µs ±4µs 1396 samples
'Clear 4/4' Mean time: 1235µs ±14µs 1390 samples
'Clear 16/16' Mean time: 4507µs ±42µs 1405 samples
As you can see clearing 65K pixels is best done in one call with the actual javascript call adding about 100µs to do.
On Firefox the number of pixels to clear does not affect the execution time of the call to clearRect with a call clearRect(0,0,256,256) and clearRect(0,0,16,16) both taking ~2µs
Apart from the speed, trying to clear overlapping animated sprites per sprite becomes extremely complicated and can result in 100s of clear calls with only a dozen sprites. This is due to the overlapping.

HTML5 Canvas - Redrawing new circles after erasing them with clip

I have a unique problem.
I am creating a game of snake with HTML5 and Canvas
I have a function that generates apples on the board randomly and removes them after a set period of time. In order to remove circles, you have to use the clip() function followed by clearRect().
However, after you use the clip function, you can no longer draw new circles.
The solution I found was using ctx.save() and ctx.restore(). However, if you play the game, you will learn that the snake acts crazy when circles disappear and new circles appear.
I suspect this has to do with my use of the save and restore functions.
Here's the specific code in question
var width = canvas.width;
var height = canvas.height;
var applesArray = []; // Store the coordinates generated randomly
// Generates a random coordinate within the confines of the canvas and pushes it to the apples array
function randCoord() {
var coord = Math.floor(Math.random() * height);
applesArray.push(coord);
return coord;
}
function generateApples() {
ctx.beginPath();
ctx.fillStyle = "green";
ctx.arc(randCoord(),randCoord(),3,0, 2 * Math.PI);
ctx.fill();
ctx.save(); // To redraw circles after clip, we must use save
ctx.clip(); // Allows only the circle region to be erased
setTimeout(function() {
ctx.clearRect(0, 0, width, height);
},3000);
ctx.restore(); // We must restore the previous state.
}
setInterval(function() {
generateApples();
},4000);
You can play the game here
https://jsfiddle.net/2q1svfod/9/
Can anyone explain this weird behavior? I did not see it coming?
The code has multiple issues.
The code that draws the snake (e.g. upArrow function) simply extends the current path. This is a problem because the code that draws the apple starts a new path. Note that save/restore in apple drawing code does not help because path is not part of the state that is saved/restored. The code that draws the snake will need to start a new path. For example...
function upArrow() {
if (direction == "up") {
goUp = setInterval(function() {
ctx.beginPath();
ctx.moveTo(headX, headY);
ctx.lineTo(headX, headY - 10);
ctx.stroke();
headY -= 10;
}, 400);
}
}
The save/clip/restore calls are in the code that draws the apple. These methods need to be moved into the timeout callback function that erases the apple. Also, the code that erases the apple will need to recreate the path (because the snake drawing could have changed the path between when the apple is drawn and when the apple is erased). For example...
function generateApples() {
var cx = randCoord();
var cy = randCoord();
ctx.beginPath();
ctx.fillStyle = "green";
ctx.arc(cx, cy,3,0, 2 * Math.PI);
ctx.fill();
setTimeout(function() {
ctx.beginPath();
ctx.arc(cx,cy,3,0, 2 * Math.PI);
ctx.save();
ctx.clip();
ctx.clearRect(0, 0, width, height);
ctx.restore();
},40000);
}
These changes will get you close to what you intended. However, there will still be a couple minor issues.
When drawing the apple, there will be some anti-aliasing occuring around the edge of the apple's path. The clear operation can miss clearing some of these pixels. After the clear operation, you might see a semi-transparent outline of where the apple was. You could work around this issue by using a slightly larger circle radius when clearing the apple.
Another issue is that apples could be drawn on top of the snake. Erasing the apple will also erase the snake. There is not an easy fix for this issue. You would need to store all the coordinates for the snake and then redraw all or part of the snake.
In the long term, you may want to consider the suggestions in the comments about restructuring your logic to track all objects and redraw everything each frame (or redraw everything after each change).

Using setInterval with requestAnimationFrame

I'd like to use setInterval for actual steps in a program and requestAnimationFrame for rendering. I was under the impression this would be the perfect mix: rendering speed would not slow down actual progression, so dips in frame rate would only affect frame rate and still sync up. What I got, however, were drops in the setInterval function as well.
An example of what I mean, intentionally bogged down with shadows: http://jsfiddle.net/Ltdz168m/
Reduce or increase the number of refills and you'll see the difference
It would appear that requestAnimationFrame is actually not a solution to rendering lag slowing down JavaScript. How then would I update the logic side of things, which can be done at 60 fps, without hindrance from rendering? Are WebWorkers absolutely necessary?
The problem is that the browser is still only capable of doing one thing at a time. So, if it's rendering, it can't update the position.
When doing stuff to support variable framerates, you should always use Delta Timing. It works something like this:
requestAnimationFrame(function(e) {
document.getElementById('status').innerHTML = "Delta time: "+e;
// lag randomly
while(Math.random() > 0.0000001) {}
requestAnimationFrame(arguments.callee);
});
<div id="status"></div>
As you can see (hopefully), regardless of framerate, the delta time shown goes up consistently. This means you can do, for example, angleFromStart = e/1000*Math.PI*2; and your dot will orbit at precisely 60 RPM.
var angle=0,
radian=Math.PI/180;
var canvas=document.getElementById("canvas"),
context=canvas.getContext("2d");
context.shadowColor="black";
context.shadowBlur=100;
requestAnimationFrame(function draw(e) {
angle = e/1000*Math.PI*2;
var x=canvas.width/2+Math.cos(angle)*canvas.width/4,
y=canvas.height/2+Math.sin(angle)*canvas.height/4;
context.clearRect(0, 0, canvas.width, canvas.height);
context.beginPath();
context.arc(x, y, 5, 0, Math.PI*2);
context.closePath();
for(var i=0; i<255; i++) {
context.fill();
}
requestAnimationFrame(draw);
});
#canvas {
border: 1px solid black;
}
<canvas id="canvas"></canvas>
PS: I love the new Stack Snippet feature!

HTML 5 canvas clip very costly

I have serious performance problems using canvas clip() with chrome.
I have made a test case to illustrate.
Even in a simple case like this the red rectangle blinks as if it takes too much time to redraw, and a CPU profiling shows the clip() method takes about 10% of the CPU.
In my real program, it get to 16% and keep increasing each frame until the canvas almost freezes the browser..
Is there something wrong in my use of clip ?
Thank you for any suggestions,
Regards.
Cause
Insert a beginPath() as rect() adds to the path unlike fillRect()/strokeRect(). What happens here is that the rectangles are accumulating eventually slowing the clipping down over time.
This in combination with using setInterval, which is not able to synchronize with monitor/screen refreshes, worsens the problem.
To elaborate on the latter:
Using setInterval()/setTimeout() can cause tearing which happens when the draw is in the "middle" of its operation not fully completed and a screen refresh occur.
setInterval can only take integer values and you would need for 16.67 ms to synchronize frame-wise (#60Hz). Even if setInterval could take floats it would not synchronize the timing with the monitor timing as the timer mechanism isn't bound to monitor at all.
To solve this always use requestAnimationFrame to synchronize drawings with screen updates. This is directly linked to monitor refreshes and is a more low-level and efficient implementation than the other, and is made for this purpose, hence the name.
Solution embedding both fixes above
See modified bin here.
The code for future visitors:
function draw() {
context.fillStyle = '#000';
context.fillRect(0, 0, width, height);
context.save();
context.beginPath(); /// add a beginPath here
context.rect(0, 0, 100, 100);
context.clip();
context.fillStyle = '#ff0000';
context.fillRect(0, 0, 200, 200);
context.restore();
requestAnimationFrame(draw); /// use rAF here
}
canvas.width = width;
canvas.height = height;
canvas.style.width = width+'px';
canvas.style.height = height+'px';
requestAnimationFrame(draw); /// start loop
PS: If you need to stop the loop inject a condition to run rAF all inside the loop, ie:
if (isPlaying) requestAnimationFrame(draw);
There is BTW no need for closePath() as this will be done implicit for you as by the specs. You can add it if you want, but calling clip() (or fill() etc.) will do this for you (specs is addressing browser implementers):
Open subpaths must be implicitly closed when computing the clipping region

Categories

Resources