In JS, how to have multiple drawings at different speeds? - javascript

I’ve got drawings of planets that revolve around the sun. They rotate fine, but they need to be positioned accurately. They move at the same speed so it doesn’t look realistic.
I would like to know how to make some planets swing a lot faster than others but they’re all in the same animation function and trying to make several animation functions just causes errors.
Here's my relevant code:
function rotate_point(pointX, pointY, originX, originY, ang) {
ang = Math.PI / 180.0;
return {
x: Math.cos(ang) * (pointX-originX) - Math.sin(ang) * (pointY-originY) + originX ,
y: Math.sin(ang) * (pointX-originX) + Math.cos(ang) * (pointY-originY) + originY
};
}
, Venus: {
render: function(){
ctx.beginPath();
gravityVenus = rotate_point(gravityVenus.x, gravityVenus.y, dynamicSunX, dynamicSunY, angleOfSun); //the positions are dynamic based on what the canvas height and width are
ctx.arc(gravityVenus.x,gravityVenus.y ,7, 0, 2*Math.PI);
ctx.fillStyle = "rgba(255,165,0,1)";
ctx.closePath();
ctx.fill();
}
}
, Mercury: {
render: function(){
ctx.beginPath();
gravityMercury = rotate_point(gravityMercury.x, gravityMercury.y, dynamicSunX, dynamicSunY - 2, angleOfSun);
ctx.arc(gravityMercury.x,gravityMercury.y ,5, 0, 2*Math.PI);
ctx.fillStyle = "rgba(119,136,153,1)";
ctx.closePath();
ctx.fill();
ctx.stroke();
}
function animate(){
background.render();
Solarsystem.Neptune.render();
Solarsystem.Uranus.render();
Solarsystem.Saturn.render();
Solarsystem.Jupiter.render();
Solarsystem.Mars.render();
Solarsystem.Earth.render();
Solarsystem.Venus.render();
Solarsystem.Mercury.render();
Solarsystem.Sun.render();
}
var animateInterval = setInterval(animate, 1000/60); //this sets the speed for everything in the Solarsystem array.
I’ve found a snippet from somewhere that might help you but didn’t help me adjust , I tried to play with it but I couldn’t get it working. Here it is.
var time = new Date();
ctx.arc( ((2*Math.PI)/60)*time.getSeconds() + ((2*Math.PI)/60000)*time.getMilliseconds() );
enter code here
Could someone help me out? Thanks!

Related

ReactJS: HTML5 Canvas render error in iOS device (>= iPhone 8)

I try to create a countdown clock using canvas and useEffect. It work fine until I test it in iPhone's browser (all device >= iPhone 8). When my clock running on iPhone 8, sometimes it has issue look like in picture.
This issue don't happend in other device (android, laptop, iphone <= 6)
Please help me!
[![`
useEffect(() => {
console.log('percent canvas = ', percent);
// var canvas = document.getElementById("clockCanvas");
const canvas = canvasRef.current;
const ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, width, width);
ctx.lineWidth = strokeWidth;
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, 2 * Math.PI);
ctx.fillStyle = backgroundClock;
ctx.fill();
ctx.closePath();
ctx.beginPath();
ctx.arc(cx, cy, radius, 0, 2 * Math.PI);
ctx.strokeStyle = colorStroke;
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.arc(cx, cy, radius, -0.5 * Math.PI + (2 * (percent / 100) * Math.PI), - 0.5 * Math.PI, true);
ctx.strokeStyle = strokeColor;
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.lineWidth = textStrokeWidth;
ctx.strokeStyle = textStrokeColor;
ctx.font = `${textSize}px Roboto`;
ctx.textAlign = "center";
ctx.textBaseline = 'middle';
ctx.strokeText(text, width / 2, width / 2, width - 2 * strokeWidth);
ctx.closePath();
},
[
cx,
cy,
radius,
width,
percent,
strokeColor,
strokeWidth,
text,
textSize,
textStrokeColor,
textStrokeWidth
]);
return (
<canvas id="clockCanvas" width={width} height={width} ref={canvasRef}>
</canvas>
);
};
export default Clock;
`]2]2
Hey there Do Anh Bon!
I know this question is 8 months old, so I'm assuming that you found a solution, but in case you did not...
I am working on a web development project and encountered a similar error. I spent quite some time doing research on the topic and I believed that I had tried nearly everything (changing image rendering settings in CSS, messing with device pixel ratio in various ways, etc.). None of the suggestions even came close to fixing the problem. Some of them actually made the code more verbose and the resulting image less appealing on top of that.
What I finally found to be the solution was to simply use the close path function INSTEAD of drawing the last iteration (meaning the last line) of my drawing. What this does is simply connect the line to the starting position.
function drawShape(x, y, radius, inset, n) {
ctx.fillStyle = "hsl(" + hue + ",100%,50%)";
ctx.beginPath();
ctx.save();
ctx.translate(x, y)
ctx.moveTo(0, -radius);
for (let i = 0; i < n; i++) {
ctx.rotate(Math.PI / n);
ctx.lineTo(0, -radius * inset);
ctx.rotate(Math.PI / n);
if (i === n - 1) {
break;
} else {
ctx.lineTo(0, -radius);
}
}
ctx.restore();
ctx.closePath();
ctx.stroke();
ctx.fill();
}
Keep in mind that since you are attempting to draw a curved line, this solution may not work for you (I have not tried it out with your code).
If this solution does not work, then another thing you might want to try to to omit the use of close path and see what results you get then.
I really hope this helps!
-Keith
Before
After

multiple progress circle same page

Inspiring myself from this answer, I want to make several progress circle in my page (about 30). It works perfectly for one but I don't get the circle for the values. I however get the percentage displayed correctly.
I've tried various thing, adding [count] to most of the options but still the circle is not drawn for each cell.
I added my code in this Fiddle.
Can you see what's wrong?
Your drawCircle function needs a bit more information (ctx and radius)
var drawCircle = function(ctx, radius, color, lineWidth, percent) {
percent = Math.min(Math.max(0, percent || 1), 1);
ctx.beginPath();
ctx.arc(0, 0, radius, 0, Math.PI * 2 * percent, false);
ctx.strokeStyle = color;
ctx.lineCap = 'round'; // butt, round or square
ctx.lineWidth = lineWidth;
ctx.stroke();
};
and you need to pass it that info when using it:
drawCircle(ctx[count], radius[count], '#efefef', options[count].lineWidth, 100 / 100);
drawCircle(ctx[count], radius[count], color[count], options[count].lineWidth, options[count].percent / 100);
like here: https://fiddle.jshell.net/6ooL53pp/3/

Issue on Drawing Multiple Circles on HTML5 Canvas

Can you please take a look at this demo and let me know how I can draw multiple circles in a canvas with different coordinate without repeating bunch of codes?
As you can see on Demo and following code
var ctx = $('#canvas')[0].getContext("2d");
ctx.fillStyle = "#00A308";
ctx.beginPath();
ctx.arc(150, 50, 5, 0, Math.PI * 2, true);
ctx.arc(20, 85, 5, 0, Math.PI * 2, true);
ctx.arc(160, 95, 5, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
I tried to have them under ctx but it is not correct so I tried to use for loop to create 50 points but I have issue on repeating and adding code like ctx.fill(); for all of them.
Can you please let me know how I can fix this?
Thanks
Constantly creating and closing new paths is not good advice.
You should batch together all fills / strokes of the same style, and execute them in a single draw call. The performance difference between these approaches becomes apparent very quickly with increasing polygon count.
The way to go about it is to move the pen and make the path construction call for each circle; stroke/fill in the end in one shot. However, there's a quirk involved here. When you have the point moved to the center and draw the circle, you'd still see a horizontal radius line, drawn from the center of the circle, to the circumference.
To avoid this artefact, instead of moving to the center, we move to the circumference. This skips the radius drawing. Essentially all these commands are for tracing a path and there's no way to describe a discontinuity without calling closePath; usually moveTo does it but HTML5 canvas API it doesn't. This is a simple workaround to counter that.
const pi2 = Math.PI * 2;
const radius = 5;
ctx.fillStyle = '#00a308';
ctx.beginPath();
for( let i=0, l=coords.length; i < l; i++ )
{
const p = coords[i],
x = p.x,
y = p.y;
ctx.moveTo( x + radius, y ); // This was the line you were looking for
ctx.arc( x, y, radius, 0, pi2 );
}
// Finally, draw outside loop
ctx.stroke();
ctx.fill();
Also worth considering, is using transformations, and drawing everything relative to a local frame of reference.
ctx.fillStyle = '#00a308';
ctx.beginPath();
for( let i=0, l=coords.length; i < l; i++ )
{
const p = coords[i];
ctx.save();
ctx.translate( p.x + radius, p.y );
ctx.moveTo( 0, 0 );
ctx.arc( 0, 0, radius, 0, pi2 );
ctx.restore();
}
ctx.stroke();
ctx.fill();
This is because you are not closing the path, either using fill() or closePath() will close the path so it does not try and connect all the items. fill() fills in the circles and closes the path so we can just use that. Also you need to use beginPath(), so that they are separate from each other. Here is your three circles:
var coords = [ [150,50], [20,85], [160,95] ];
for(var i = 0; i < coords.length; i++){
ctx.beginPath();
ctx.arc(coords[i][0], coords[i][1], 5, 0, Math.PI * 2, true);
ctx.fill();
}
To not repeat a bunch of code and have unique coordinates store your X and Y position in an array and use a for loop to go through it.
Update:
A more efficient way to do which achieves the same effect this would be to only use a single path and use moveTo() instead of creating a new path when drawing each circle:
ctx.beginPath();
for(var i = 0; i < coords.length; i++){
ctx.moveTo(coords[i][0], coords[i][1]);
ctx.arc(coords[i][0], coords[i][1], 5, 0, Math.PI * 2, true);
}
ctx.fill();
ctx.beginPath();
points.forEach(point => {
ctx.moveTo( point.x, point.y );
ctx.arc(point.x,point.y,1,0,Math.PI*2,false);
});
ctx.fill();
You can easily create several circles with a for loop. You really just need to draw an arc and fill it each time. Using your example, you could do something like this.
var ctx = $('#canvas')[0].getContext("2d");
ctx.fillStyle = "#00A308";
for (var i = 0; i < 3; i++) {
ctx.arc(50 * (i+1), 50 + 15 * i, 5, 0, Math.PI * 2, true);
ctx.fill();
}
Example Fiddle of lots of circles in different locations drawn in a loop.
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext( '2d' );
var cx = canvas.width/2;
var cy = canvas.height/2;
ctx.fillStyle = "#00A308";
var total_circles = 50;
var radius = 100;
for(i = 0; i < total_circles; i++){
var angle = i * 2 * Math.PI/total_circles;
var x = cx + Math.cos(angle) * radius;
var y = cy + Math.sin(angle) * radius;
ctx.beginPath();
ctx.arc(x, y, 2, 0, Math.PI * 2, true);
ctx.closePath();
ctx.fill();
}

Rotation logic in html5 canvas

I have this animation, but i cant get over the logic. I hope someone can help me here.
Basicly i need this: http://jsfiddle.net/PDE85/9/ but without the arrow doing such crazy moves. It should be attached to the front of the open circle to simulate an expanding arrow.
I got the triangle to turn right here but it doesnt work when i mix it with position logic as seen in the first example.
Here is the code for reference
(function() {
var size = ($(window).height()/5)*4;
$("#intro-container").css('width',size);
$("#intro-canvas").css('width',size);
$("#intro-canvas").css('height',size);
var interval = window.setInterval(draw, 30);
var degrees = 0.0;
var offset = 20;
var rotate = 0;
var canvas = document.getElementById('intro-canvas');
var ctx = canvas.getContext('2d');
canvas.width = size;
canvas.height = size;
draw();
function draw() {
if (canvas.getContext) {
ctx.fillStyle="white";
ctx.strokeStyle="white";
ctx.clearRect(0, 0, size, size);
ctx.save();
ctx.translate(size/2, size/2);
ctx.rotate(-90 * Math.PI / 180);
ctx.beginPath();
ctx.lineWidth = size/8;
ctx.arc(0, 0, size/3, 0, rotate * Math.PI / 180);
//ctx.shadowBlur=1;
//ctx.shadowColor="black";
ctx.stroke();
ctx.restore();
ctx.beginPath();
ctx.save();
// moving logic
ctx.translate(size/2, size/2);
ctx.rotate(-Math.PI / 180 * -rotate+1);
ctx.translate(-size/3, -size/3);
// rotating logic
ctx.translate(size/2, size/2);
ctx.rotate((rotate * Math.PI + 420) / 180);
ctx.moveTo(0,0);
ctx.lineTo(size/6,0);
ctx.lineTo(0,size/6);
ctx.lineTo(0,0);
ctx.fill();
ctx.restore();
rotate += 1;
if(rotate > 360){
window.clearInterval(interval)
}
}
}
})();
I believe you are looking for this : http://jsfiddle.net/PDE85/12/
The rotation comes from, the rotate call which is unnecessary.
Plus you need an inverted triangle, hence the coordinates needed an update:
...
// ctx.rotate((rotate * Math.PI + 420) / 180);
ctx.moveTo(0,0);
ctx.lineTo(-size/6,0);
ctx.lineTo(0,-size/6);
...

clearing circular regions from HTML5 Canvas

It appears the only way to clear a region from a canvas is to use the clearRect() command - I need to clear a circle (I am masking out areas from a filled canvas, point lights in this specific case) and despite all attempts it does not seem possible.
I tried drawing a circle with an alpha value of 0 but simply nothing would appear unless the alpha was higher (which is counter to the point :P) - I assume because a contex.fill() draws it as an add rather than a replace.
Any suggestions on how I might be able to (quickly) clear circles for mask purposes?
Use .arc to create a circular stroke and then use .clip() to make that the current clipping region.
Then you can use .clearRect() to erase the whole canvas, but only the clipped area will change.
If you're making a game or something where squeezing every bit of performance matters, have a look at how I made this answer: Canvas - Fill a rectangle in all areas that are fully transparent
Specifically, the edit of the answer that leads to this: http://jsfiddle.net/a2Age/2/
The huge plusses here:
No use of paths (slow)
No use of clips (slow)
No need for save/restore (since there's no way to reset a clipping region without clearing all state(1), it means you must use save/restore also)
(1) I actually complained about this and resetClip() has been put in the offical spec because of it, but it will be a while before browsers implement it.
Code
var ctx = document.getElementById('canvas1').getContext('2d'),
ambientLight = 0.1,
intensity = 1,
radius = 100,
amb = 'rgba(0,0,0,' + (1 - ambientLight) + ')';
addLight(ctx, intensity, amb, 200, 200, 0, 200, 200, radius); // First circle
addLight(ctx, intensity, amb, 250, 270, 0, 250, 270, radius); // Second circle
addLight(ctx, intensity, amb, 50, 370, 0, 50, 370, radius, 50); // Third!
ctx.fillStyle = amb;
ctx.globalCompositeOperation = 'xor';
ctx.fillRect(0, 0, 500, 500);
function addLight(ctx, intsy, amb, xStart, yStart, rStart, xEnd, yEnd, rEnd, xOff, yOff) {
xOff = xOff || 0;
yOff = yOff || 0;
var g = ctx.createRadialGradient(xStart, yStart, rStart, xEnd, yEnd, rEnd);
g.addColorStop(1, 'rgba(0,0,0,' + (1 - intsy) + ')');
g.addColorStop(0, amb);
ctx.fillStyle = g;
ctx.fillRect(xStart - rEnd + xOff, yStart - rEnd + yOff, xEnd + rEnd, yEnd + rEnd);
}
canvas {
border: 1px solid black;
background-image: url('http://placekitten.com/500/500');
}
<canvas id="canvas1" width="500" height="500"></canvas>
Given the requirements, these answers are fine. But lets say you're like me and you have additional requirements:
You want to "clear" a part of a shape that may be partially outside the bounds of the shape you're clearing.
You want to see the background underneath the shape instead of clearing the background.
For the first requirement, the solution is to use context.globalCompositeOperation = 'destination-out' The blue is the first shape and the red is the second shape. As you can see, destination-out removes the section from the first shape.
Here's some example code:
explosionCanvasCtx.fillStyle = "red"
drawCircle(explosionCanvasCtx, projectile.radius, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
explosionCanvasCtx.globalCompositeOperation = 'destination-out' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
drawCircle(explosionCanvasCtx, projectile.radius + 20, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
Here's the potential problem with this: The second fill() will clear everything underneath it, including the background. Sometimes you'll want to only clear the first shape but you still want to see the layers that are underneath it.
The solution to that is to draw this on a temporary canvas and then drawImage to draw the temporary canvas onto your main canvas. The code will look like this:
diameter = projectile.radius * 2
console.log "<canvas width='" + diameter + "' height='" + diameter + "'></canvas>"
explosionCanvas = $("<canvas width='" + diameter + "' height='" + diameter + "'></canvas>")
explosionCanvasCtx = explosionCanvas[0].getContext("2d")
explosionCanvasCtx.fillStyle = "red"
drawCircle(explosionCanvasCtx, projectile.radius, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
explosionCanvasCtx.globalCompositeOperation = 'destination-out' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
durationPercent = (projectile.startDuration - projectile.duration) / projectile.startDuration
drawCircle(explosionCanvasCtx, projectile.radius + 20, projectile.radius, projectile.radius)
explosionCanvasCtx.fill()
explosionCanvasCtx.globalCompositeOperation = 'source-over' #see https://developer.mozilla.org/samples/canvas-tutorial/6_1_canvas_composite.html
ctx.drawImage(explosionCanvas[0], projectile.pos.x - projectile.radius, projectile.pos.y - projectile.radius) #center
You have a few options.
Firstly, here's a function we'll use to fill a circle.
var fillCircle = function(x, y, radius)
{
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
context.fill();
};
clip()
var clearCircle = function(x, y, radius)
{
context.beginPath();
context.arc(x, y, radius, 0, 2 * Math.PI, false);
context.clip();
context.clearRect(x - radius - 1, y - radius - 1,
radius * 2 + 2, radius * 2 + 2);
};
See this on jsFiddle.
globalCompositeOperation
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();
};
See this on jsFiddle.
Both gave the desired result on screen, however the performance wasn't sufficient in my case as I was drawing and clearing a lot of circles each frame for an effect. In the end I found a different way to get a similar effect to what I wanted by just drawing thicker lines on an arc, but the above may still be useful to someone having different performance requirements.
Use canvas.getContext("2d").arc(...) to draw a circle over the area with the background colour?
var canvas = document.getElementById("myCanvas");
var context = canvas.getContext("2d");
context.arc(x, y, r, 0, 2*Math.PI, false);
context.fillStyle = "#FFFFFF";
context.fill();
Where x = left position, y = right position, r = radius, and ctx = your canvas:
function clearCircle( x , y , r ){
for( var i = 0 ; i < Math.round( Math.PI * r ) ; i++ ){
var angle = ( i / Math.round( Math.PI * r )) * 360;
ctx.clearRect( x , y , Math.sin( angle * ( Math.PI / 180 )) * r , Math.cos( angle * ( Math.PI / 180 )) * r );
}
}

Categories