Javascript canvas behaving strangely in for loop - javascript

I'm trying to create a canvas graphic by rotating ellipses around a central point. When I do this however, the for loop creates a strange image. There are sporadic gaps in between ellipses, and the colors are strange. At first I thought I was calculating the angles wrong, but if that was the case, the gaps should not be sporadic as I am incrementing them in equal increments each iteration of the for loop. Then I thought maybe I was looping around too many times, but if that was the case, only the colors should be off and not the gaps. This makes me think it's an asynchronous problem where maybe the context didn't finish shifting before the next iteration started drawing the next circle, but that's just a guess. What could be wrong and how do I fix it?
Here's a jsfiddle, there's only 6 ellipses when there should be 10, what's happening?
https://jsfiddle.net/vj7csL3b/
<!DOCTYPE html>
<html>
<body>
<canvas id="canvas" width="200" height="200"></canvas>
<script>
const canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext("2d");
let color = "#0000ff";
let numberOfEllipses = 10;
let lineThickness = 3;
let middleW = 150;
let middleL = 150;
let eWidth = 50;
let eHeight = 50;
let fill = "rgba(0,0,255,0.2)";
let shift = 50;
let rotate = ((Math.PI / 180) * 360) / 10;
//console.log(middleW, middleL, dfc);
for (let i = 1; i < 10 + 1; i++) {
//translate to point of rotation, rotate and translate back
ctx.translate(middleW, middleL);
ctx.rotate(rotate * i);
ctx.translate(-1 * middleW, -1 * middleL);
ctx.beginPath();
// draws ellipse shifted from the middle we are rotating around.
ctx.ellipse(
middleW + shift,
middleL + shift,
eWidth,
eHeight,
0,
0,
2 * Math.PI
);
ctx.strokeStyle = color;
ctx.lineWidth = lineThickness;
ctx.fillStyle = fill;
ctx.stroke();
ctx.fill();
}
</script>
</body>
</html>

The circles were actually overlapping over each other.Altering the rotation angle and shift will give the output that u expect.
const canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
const ctx = canvas.getContext("2d");
let color = "#0000ff";
let numberOfEllipses = 10;
let lineThickness = 3;
let middleW = 150;
let middleL = 150;
let eWidth = 50;
let eHeight = 50;
let fill = "rgba(0,0,255,0.2)";
let shift = 60;
let rotate = ((Math.PI / 180) * 360) / 60;
//console.log(middleW, middleL, dfc);
for (let i = 1; i < 10 + 1; i++) {
//console.log("paiting circle" + i);
ctx.translate(middleW, middleL);
ctx.rotate(rotate * i);
ctx.translate(-1 * middleW, -1 * middleL);
ctx.beginPath();
ctx.ellipse(
middleW + shift,
middleL + shift,
eWidth,
eHeight,
0,
0,
2 * Math.PI
);
ctx.strokeStyle = color;
ctx.lineWidth = lineThickness;
ctx.fillStyle = fill;
ctx.stroke();
ctx.fill();
}

Related

HTML5 Canvas sub-pixel position rendering issue

I'm trying to draw multiple rectangles positioned very densely in a straight line so they form a single shape like so:
const canvas = document.getElementById("c");
const ctx = canvas.getContext("2d");
const a = 87;
const l = 300;
const col = 'rgba(0,0,64)';
const q = 4; // density - change
ctx.moveTo(300, 100);
ctx.lineTo(
300 + (Math.cos(degtorad(a)) * l),
100 + (Math.sin(degtorad(a)) * l)
);
ctx.strokeStyle = col;
ctx.lineWidth = 3;
ctx.stroke();
stack(l, a, col);
function stack(length, angle, color) {
ctx.fillStyle = color;
for (let i = 1; i < length * q; i++) {
let x = 100 + (Math.cos(degtorad(angle)) * i / q);
let y = 100 + (Math.sin(degtorad(angle)) * i / q);
console.log(`x:${x}, y:${y}`);
ctx.beginPath();
ctx.rect(x, y, 150, 100);
ctx.fill();
}
}
function degtorad(angle) {
return angle * (Math.PI / 180.0);
}
https://jsfiddle.net/fallenartist/qeo7a1gx/
However, I don't like the jagged edge at all! It looks like there are issues with canvas rendering at sub-pixel coordinates. Notice how rendering of edges of absolutely positioned rectangles differ from a straight line:
Can anyone explain why is this? And how it can be improved?

How to get rid of faint dark lines in a pie chart with canvas?

I'm making a color circle that acts like a pie chart, which is made from a list [0..n]. The issue is that there are some faint dark lines between each slice. It is especially noticeable on the green colors as shown here.
Here is my code:
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
const list = Array.from({ length: 100 }, (_, i) => i + 1); // generate list
context.fillStyle = '#000000';
context.fillRect(0, 0, canvas.width, canvas.height);
const center = [Math.floor(canvas.width / 2), Math.floor(canvas.height / 2)];
const radius = Math.min(center[0], center[1]);
let prevAngle = 0;
for (let i = 0; i < list.length; i++) {
const hue = 255 - (list[i] / list.length) * 360;
const color = `hsl(${hue},100%,50%)`;
const angle = prevAngle + (1 / list.length) * Math.PI * 2;
context.fillStyle = color;
context.strokeStyle = color;
context.beginPath();
context.arc(center[0], center[1], radius, prevAngle, angle);
context.lineTo(center[0], center[1]);
context.fill();
context.stroke();
prevAngle = angle;
}
<canvas id="canvas" width="600" height="600"></canvas>
It looks like this is due to imprecisions in subpixels. You could make it less obvious by making your lines a tiny bit thicker:
context.lineWidth = 1.5;
const canvas = document.getElementById('canvas');
const context = canvas.getContext('2d');
const list = Array.from({ length: 100 }, (_, i) => i + 1); // generate list
context.fillStyle = '#000';
context.lineWidth = 1.5; // <-------------
context.fillRect(0, 0, canvas.width, canvas.height);
const center = [Math.floor(canvas.width / 2), Math.floor(canvas.height / 2)];
const radius = Math.min(center[0], center[1]);
let prevAngle = 0;
for (let i = 0; i < list.length; i++) {
const hue = 255 - (list[i] / list.length) * 360;
const color = `hsl(${hue},100%,50%)`;
const angle = prevAngle + (1 / list.length) * Math.PI * 2;
context.fillStyle = color;
context.strokeStyle = color;
context.beginPath();
context.arc(center[0], center[1], radius, prevAngle, angle);
context.lineTo(center[0], center[1]);
context.fill();
context.stroke();
prevAngle = angle;
}
<canvas id="canvas" width="600" height="600"></canvas>

How can I calculate points on a circular path? (Center known)

I'm currently working on a small HTML canvas game (zero point of the canvas top-left). I need the coordinates (X,Y) on a circle.
The radius and the center are known.
My variables:
var radius = 50;
var center_x = 200;
var center_y = 200;
var angle = 45;
The formula for a point on a circle, given the angle, is:
x = xcenter + r·cos(𝛼)
y = ycenter + r·sin(𝛼)
...where 𝛼 is the angle in radians.
Since on a web page the Y coordinate is downwards, you would subtract the term in the formula.
Here is a demo, where the angle changes continually:
var radius = 50;
var center_x = 100;
var center_y = 100;
var angle = 50; // Will change in this demo
const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const span = document.querySelector("span");
const loop = () => {
angle = (angle + 1) % 360;
// Formula:
var rad = angle * Math.PI / 180;
var x = center_x + radius * Math.cos(rad);
var y = center_y - radius * Math.sin(rad);
// Draw point
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "#ff2626";
ctx.beginPath();
ctx.arc(x, y, 2, 0, Math.PI * 2, true);
ctx.fill();
// Display angle
span.textContent = angle;
// Repeat at next paint cycle
requestAnimationFrame(loop);
};
loop();
<div>Angle: <span></span></div>
<canvas width=500 height=160></canvas>

Divide whole canvas in equal columns

I am trying to divide my whole canvas in 20 equal columns and animate them individually on the y-axis. The goal is to create a similar wave scroll animation like here. First I tried to create the wave scroll effect with php generated images with the text on it and tried to animate them in single divs with the images as background-images. It technically worked but the performance and page load time was extremely bad. Now I want to create it with canvas: I already have the content with all the images and text in it and tried to animate it. I tried to save the whole content in columns (rectangles) with getImageData() then I created created rectangles with the ImageData and re-draw them in a loop but again the performance was terrible, especially on mobile devices. The animation loop looked as followed:
var animate = function(index, y) {
// The calculations required for the step function
var start = new Date().getTime();
var end = start + duration;
var current = rectangles[index].y;
var distance = y - current;
var step = function() {
// get our current progress
var timestamp = new Date().getTime();
var progress = Math.min((duration - (end - timestamp)) / duration, 1);
context.clearRect(rectangles[index].x, rectangles[index].y, columnWidth, canvas.height);
// update the rectangles y property
rectangles[index].y = current + (distance * progress);
context.putImageData(rectangles[index].imgData, rectangles[index].x, rectangles[index].y);
// if animation hasn't finished, repeat the step.
if (progress < 1)
requestAnimationFrame(step);
};
// start the animation
return step();
};
Now the question: How can I divide the whole canvas in equal columns and animate them on the y-axis with a good performance? Any suggestions? Maybe with the help of Pixi.js / Greensock? Thanks in advance!
Do not use getImageData and setImageData to do animation. The canvas is an image and can be rendered just like any image.
To do what you want
Create a second copy of the canvas and use that canvas a source for the strips you want to render.
Example.
const slices = 20;
var widthStep;
var canvas = document.createElement("canvas");
var canvas1 = document.createElement("canvas");
canvas.style.position = "absolute";
canvas.style.top = canvas.style.left = "0px";
var ctx = canvas.getContext("2d");
var ctx1 = canvas1.getContext("2d");
var w = canvas.width;
var h = canvas.height;
function resize(){
canvas.width = canvas1.width = innerWidth;
canvas.height = canvas1.height = innerHeight;
w = canvas.width;
h = canvas.height;
ctx1.font = "64px arial black";
ctx1.textAlign = "center"
ctx1.textBaseLine = "middle";
ctx1.fillStyle = "blue";
ctx1.fillRect(0,0,w,h);
ctx1.fillStyle = "red";
ctx1.fillRect(50,50,w-100,h-100);
ctx1.fillStyle = "black";
ctx1.strokeStyle = "white";
ctx1.lineWidth = 5;
ctx1.lineJoin = "round";
ctx1.strokeText("Waves and canvas",w / 2, h / 2);
ctx1.fillText("Waves and canvas",w / 2, h / 2);
widthStep = Math.ceil(w / slices);
}
resize();
window.addEventListener("resize",resize);
document.body.appendChild(canvas);
function update(time){
var y;
var x = 0;
ctx.clearRect(0,0,canvas.width,canvas.height);
for(var i = 0; i < slices; i ++){
y = Math.sin(time / 500 + i / 5) * (w / 8);
y += Math.sin(time / 700 + i / 7) * (w / 13);
y += Math.sin(time / 300 + i / 3) * (w / 17);
ctx.drawImage(canvas1,x,0,widthStep,h,x,y,widthStep,h);
x += widthStep;
}
requestAnimationFrame(update);
}
requestAnimationFrame(update);

Canvas line drawing animation

I am new learner of animation using HTML5 Canvas. I am struggling to create line drawing animation in a canvas with desired length of a line.
Here is the code
var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d"),
width = canvas.width = window.innerWidth,
height = canvas.height = window.innerHeight;
var x = 200;
var y = 200;
draw();
update();
function draw() {
context.beginPath();
context.moveTo(100, 100);
context.lineTo(x, y);
context.stroke();
}
function update() {
context.clearRect(0, 0, width, height);
x = x + 1;
y = y + 1;
draw();
requestAnimationFrame(update);
}
html,
body {
margin: 0px;
}
canvas {
display: block;
}
<canvas id="canvas"></canvas>
The line is growing on Canvas in the above code. But how to achieve that the 200px wide line and animate the movement in x and y direction. And the same animation with multiple lines using for loop and move them in different direction.
Check the reference image ....
Need to move each line in a different direction
Thanks in advance
Find a new reference image which i want to achieve
You need to either use transforms or a bit of trigonometry.
Transforms
For each frame:
Reset transforms and translate to center
Clear canvas
Draw line from center to the right
Rotate x angle
Repeat from step 2 until all lines are drawn
var ctx = c.getContext("2d");
var centerX = c.width>>1;
var centerY = c.height>>1;
var maxLength = Math.min(centerX, centerY); // use the shortest direction for demo
var currentLength = 0; // current length, for animation
var lenStep = 1; // "speed" of animation
function render() {
ctx.setTransform(1,0,0,1, centerX, centerY);
ctx.clearRect(-centerX, -centerY, c.width, c.height);
ctx.beginPath();
for(var angle = 0, step = 0.1; angle < Math.PI * 2; angle += step) {
ctx.moveTo(0, 0);
ctx.lineTo(currentLength, 0);
ctx.rotate(step);
}
ctx.stroke(); // stroke all at once
}
(function loop() {
render();
currentLength += lenStep;
if (currentLength < maxLength) requestAnimationFrame(loop);
})();
<canvas id=c></canvas>
You can use transformation different ways, but since you're learning I kept it simple in the above code.
Trigonometry
You can also calculate the line angles manually using trigonometry. Also here you can use different approaches, ie. if you want to use delta values, vectors or brute force using the math implicit.
For each frame:
Reset transforms and translate to center
Clear canvas
Calculate angle and direction for each line
Draw line
var ctx = c.getContext("2d");
var centerX = c.width>>1;
var centerY = c.height>>1;
var maxLength = Math.min(centerX, centerY); // use the shortest direction for demo
var currentLength = 0; // current length, for animation
var lenStep = 1; // "speed" of animation
ctx.setTransform(1,0,0,1, centerX, centerY);
function render() {
ctx.clearRect(-centerX, -centerY, c.width, c.height);
ctx.beginPath();
for(var angle = 0, step = 0.1; angle < Math.PI * 2; angle += step) {
ctx.moveTo(0, 0);
ctx.lineTo(currentLength * Math.cos(angle), currentLength * Math.sin(angle));
}
ctx.stroke(); // stroke all at once
}
(function loop() {
render();
currentLength += lenStep;
if (currentLength < maxLength) requestAnimationFrame(loop);
})();
<canvas id=c></canvas>
Bonus animation to play around with (using the same basis as above):
var ctx = c.getContext("2d", {alpha: false});
var centerX = c.width>>1;
var centerY = c.height>>1;
ctx.setTransform(1,0,0,1, centerX, centerY);
ctx.lineWidth = 2;
ctx.strokeStyle = "rgba(0,0,0,0.8)";
ctx.shadowBlur = 16;
function render(time) {
ctx.globalAlpha=0.77;
ctx.fillRect(-500, -500, 1000, 1000);
ctx.globalAlpha=1;
ctx.beginPath();
ctx.rotate(0.025);
ctx.shadowColor = "hsl(" + time*0.1 + ",100%,75%)";
ctx.shadowBlur = 16;
for(var angle = 0, step = Math.PI / ((time % 200) + 50); angle < Math.PI * 2; angle += step) {
ctx.moveTo(0, 0);
var len = 150 + 150 * Math.cos(time*0.0001618*angle*Math.tan(time*0.00025)) * Math.sin(time*0.01);
ctx.lineTo(len * Math.cos(angle), len * Math.sin(angle));
}
ctx.stroke();
ctx.globalCompositeOperation = "lighter";
ctx.shadowBlur = 0;
ctx.drawImage(ctx.canvas, -centerX, -centerY);
ctx.drawImage(ctx.canvas, -centerX, -centerY);
ctx.globalCompositeOperation = "source-over";
}
function loop(time) {
render(time);
requestAnimationFrame(loop);
};
requestAnimationFrame(loop);
body {margin:0;background:#222}
<canvas id=c width=640 height=640></canvas>
Here is what I think you are describing...
window.onload = function() {
var canvas = document.getElementById("canvas"),
context = canvas.getContext("2d"),
width = canvas.width = 400,
height = canvas.height = 220,
xcenter = 200,
ycenter = 110,
radius = 0,
radiusmax = 100,
start_angle1 = 0,
start_angle2 = 0;
function toRadians(angle) {
return angle * (Math.PI / 180);
}
function draw(x1, y1, x2, y2) {
context.beginPath();
context.moveTo(x1, y1);
context.lineTo(x2, y2);
context.stroke();
}
function drawWheel(xc, yc, start_angle, count, rad) {
var inc = 360 / count;
for (var angle = start_angle; angle < start_angle + 180; angle += inc) {
var x = Math.cos(toRadians(angle)) * rad;
var y = Math.sin(toRadians(angle)) * rad;
draw(xc - x, yc - y, xc + x, yc + y);
}
}
function update() {
start_angle1 += 0.1;
start_angle2 -= 0.1;
if(radius<radiusmax) radius++;
context.clearRect(0, 0, width, height);
drawWheel(xcenter, ycenter, start_angle1, 40, radius);
drawWheel(xcenter, ycenter, start_angle2, 40, radius);
requestAnimationFrame(update);
}
update();
};
html,
body {
margin: 0px;
}
canvas {
display: block;
}
<canvas id="canvas"></canvas>
This is one that is a variable length emerging pattern. It has a length array element for each spoke in the wheel that grows at a different rate. You can play with the settings to vary the results:
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
var width = canvas.width = window.innerWidth;
var height = canvas.height = window.innerHeight;
var xcenter = width/4;
var ycenter = height/2;
var radius;
var time;
if(width>height) {
radius = height*0.4;
}
else {
radius = width*0.4;
}
var start_angle1 = 0;
var start_angle2 = 0;
function toRadians (angle) {
return angle * (Math.PI / 180);
}
function draw(x1,y1,x2,y2) {
context.beginPath();
context.moveTo(x1,y1);
context.lineTo(x2,y2);
context.stroke();
}
var radmax=width;
var rads = [];
var radsinc = [];
function drawWheel(xc,yc,start_angle,count,rad) {
var inc = 360/count;
var i=0;
for(var angle=start_angle; angle < start_angle+180; angle +=inc) {
var x = Math.cos(toRadians(angle)) * rads[rad+i];
var y = Math.sin(toRadians(angle)) * rads[rad+i];
draw(xc-x,yc-y,xc+x,yc+y);
rads[rad+i] += radsinc[i];
if(rads[rad+i] > radmax) rads[rad+i] = 1;
i++;
}
}
function update() {
var now = new Date().getTime();
var dt = now - (time || now);
time = now;
start_angle1 += (dt/1000) * 10;
start_angle2 -= (dt/1000) * 10;
context.clearRect(0,0,width,height);
drawWheel(xcenter,ycenter,start_angle1,50,0);
drawWheel(xcenter,ycenter,start_angle2,50,50);
requestAnimationFrame(update);
}
function init() {
for(var i=0;i<100;i++) {
rads[i] = 0;
radsinc[i] = Math.random() * 10;
}
}
window.onload = function() {
init();
update();
};
html, body {
margin: 0px;
}
canvas {
width:100%;
height:200px;
display: block;
}
<canvas id="canvas"></canvas>

Categories

Resources