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?
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>
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>
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);
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>