Divide whole canvas in equal columns - javascript

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

Related

Javascript canvas behaving strangely in for loop

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();
}

How do I find x of where my rotated image touches a top-line

See problem illustrated here: https://i.stack.imgur.com/fuy8c.png
I have an image rotated based on an angle, and stretched to match both bottom and top line.
My question is: How do I find X of where the image touches the top line.
JsFiddle with an example http://jsfiddle.net/waaentz/htrqdwjp/43/
X is (faulty) calculated on line 28
let angle = 0; // Is dynamic
const width = 500;
const height = 100;
const padding = 100;
const canvas = document.createElement("canvas");
const stage = new createjs.Stage(canvas);
const img = new Image();
const bitmap = new createjs.Bitmap(img);
const baseGrid = new createjs.Shape();
const point = new createjs.Shape();
// Update angle each 30ms
setInterval(()=>{
if (angle++ > 90) angle = 0;
draw();
}, 30)
// Draw when image is loaded (arrow image)
img.src = getBase64();
img.addEventListener("load", ()=>{
draw();
});
// Draw function
function draw(){
const magicScaleFormular = 1 / Math.cos(angle * Math.PI / 180)
const magicXFormular = width - (width / magicScaleFormular); // <-- X formular
stage.y = padding;
stage.addChild(baseGrid, bitmap, point);
Object.assign(canvas, {width, height: height + (padding * 2)});
bitmap.regX = (img.width / 2);
bitmap.regY = img.height;
bitmap.y = height;
bitmap.rotation = angle;
bitmap.scaleY = magicScaleFormular;
point.graphics.clear()
.beginFill("red")
.drawCircle(0,0,10);
point.x = magicXFormular;
baseGrid.graphics
.beginStroke("red")
.setStrokeStyle(4)
.moveTo(0,0)
.lineTo(width, 0)
.moveTo(0, height)
.lineTo(width, height)
stage.update();
document.body.append(canvas);
}
This question is related to this: Finding size of rotated image to match top-line based on angle
To calculate one cathetus of triangle using another one, you need tangent
const magicXFormular = 100 * Math.tan(angle * Math.PI / 180);

Character doesn't move to the correct destination/wrong angle on mouse click - html canvas

EDIT: SOLVED
I'm making a game in which the character moves by right-clicking. The character is supposed to slowly move/walk (not teleport) to the destination you set by right-clicking on the canvas. The problem is I used some math to determine the angle the character should be heading towards and also wrote a function that draws a circle directly on the destination (attempting to debug). The thing is, I click on a position, the destination circle is displayed in another position and the character is walking towards another 3d position. I'm not experienced enough and I have no idea what's causing this. The math I used were pretty much copy-pasted because I don't really understand how they work so thats a problem too.
The problem is pretty much this image https://imgur.com/1lm2haM
Edit: And this image is what im trying to accomplish https://imgur.com/amQgGxM
//VARIABLES_________________________________
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var WIDTH = 640;
var HEIGHT = 360;
var CANVAS_WIDTH = 640;
var CANVAS_HEIGHT = 360;
var renderList = [];
var selfX = 250;
var selfY = 100
var frameX = 0;
var playerSpdX = 0;
var playerSpdY = 0;
var drawDest = false;
var destX;
var destY;
var Img = {};
Img.player = new Image();
Img.player.src = "Human_walk_spritesheet.png";
Img.map = new Image();
Img.map.src = "maptest.png";
Img.bullet = new Image();
Img.bullet.src = "bullet.png";
renderList.push({img: Img.player, spdX: playerSpdX, spdY: playerSpdY, currX: selfX, currY: selfY});
//CODE______________________________________
function render(){
ctx.clearRect(0,0, CANVAS_WIDTH, CANVAS_HEIGHT);
ctx.drawImage(Img.map, 0, 0);
for(i in renderList){
var renderObject = renderList[i];
renderObject.currX += playerSpdX;
renderObject.currY += playerSpdY;
selfX = renderObject.currX;
selfY = renderObject.currY;
ctx.drawImage(renderObject.img, frameX, 0, 120, 218, selfX, selfY, renderObject.img.width/31/8, renderObject.img.height/8);
}
if(drawDest)
ctx.drawImage(Img.bullet, destX, destY);
updateAnimation();
}
setInterval(render, 100);
document.onmousedown = function click(event){
if(event.button == 2){//right click
clickX = event.clientX;
clickY = event.clientY;
clickX -= selfX;
clickY -= selfY;
aimAngle = Math.atan2(clickY, clickX) / Math.PI * 180;
var spdX = Math.cos(aimAngle/180*Math.PI)*10;
var spdY = Math.sin(aimAngle/180*Math.PI)*10;
setPlayerSpeed(spdX, spdY);
drawDestinationDot(clickX, clickY);
}
}
document.oncontextmenu = function(mouse){
mouse.preventDefault();
}
function setPlayerSpeed(spdX, spdY){
playerSpdX = spdX;
playerSpdY = spdY;
}
function drawDestinationDot(x, y){
drawDest = true;
destX = x;
destY = y;
}
So after spending more than 3 entire days before and after my post combined, I finally found what the problem is. It's my fault in part that I didn't post all the code but as stack overflow suggests I tried to only post the relevant code to not confuse people who would be trying to help me and I didn't think this part I removed was relevant but it was. I'm pretty much a newbie. So the entire problem was the canvas resizing which I didn't pay any attention to. I replicated my code and just used simple rectangles instead of images and it worked perfectly because I also forgot to add the canvas resizing. That's it:
resizeCanvas();
function resizeCanvas(){
CANVAS_WIDTH = window.innerWidth - 4;
CANVAS_HEIGHT = window.innerHeight - 4;
let ratio = 16 / 9;
if(CANVAS_HEIGHT < CANVAS_WIDTH / ratio)
CANVAS_WIDTH = CANVAS_HEIGHT * ratio;
else
CANVAS_HEIGHT = CANVAS_WIDTH / ratio;
canvas.width = WIDTH;
canvas.height = HEIGHT;
canvas.style.width = '' + CANVAS_WIDTH + 'px';
canvas.style.height = '' + CANVAS_HEIGHT + 'px';
}
Thanks for helping Loïc.

Make a shape move up on a canvas

Currently, I have a canvas which is the width and height of your browser. Using this code:
var requestAnimationFrame = window.requestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.msRequestAnimationFrame;
var canvas = document.getElementById("canvas");
var width = window.innerWidth;
var height = window.innerHeight;
var circle = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
for(var i = 0; i < numofcirc; i++)
{
name = "circleno" + i;
var name = new Array(3);
name = [height, rndwidth, rndradius, vel]
circles[i] = name;
}
var vel = 2;
var circles = [];
var numofcirc = 1;
var name;
function DrawCircle()
{
rndwidth = Math.floor((Math.random() * width) + 1);
height = height - 13;
rndradius = Math.floor((Math.random() * 15) + 5);
circle.beginPath();
circle.arc(rndwidth, height, rndradius, 0, 2*Math.PI);
circle.fillStyle = "white";
circle.fill();
circle.translate(0,6);
}
function Move()
{
circle.translate(0,6);
requestAnimationFrame(Move);
}
Move();
DrawCircle();
I am able to create a circle placed randomly at the bottom of your screen. The bit of the code that isn't working is this:
function Move()
{
circle.translate(0,6);
requestAnimationFrame(Move);
}
Fireworks();
When DrawCircle(); is called, the circle is drawn on the canvas. Then Move(); is called. Becuase it uses requestAnimationFrame the function Move(); repeats over and over again. I want this code to move that circle drawn ealier up by 6, so it looks like the circle moving up.
If I add the circle.translate(0,6); to the DrawCircle(); function and change the DrawCircle(); function to this:
function DrawCircle()
{
rndwidth = Math.floor((Math.random() * width) + 1);
height = height - 13;
rndradius = Math.floor((Math.random() * 15) + 5);
circle.beginPath();
circle.arc(rndwidth, height, rndradius, 0, 2*Math.PI);
circle.fillStyle = "white";
circle.fill();
circle.translate(0,6);
requestAnimationFrame(Move);
}
DrawCircle();
then it keeps on drawing rows of circles across the screen which are all separated by 6.
Is there any way I can just make one single circle move up on your screen when it is drawn?
Thank you for you help #HelderSepu !
You should look at examples and build from that...
Here is one simple case:
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
canvas.width = canvas.height = 170;
var circles = []
circles.push({color:"red", x:120, y:120, r:15, speed:{x: 0, y: -0.5}})
circles.push({color:"blue", x:80, y:120, r:20, speed:{x: -0.5, y: -2.5}})
circles.push({color:"green", x:40, y:120, r:5, speed:{x: -1.5, y: -1.0}})
function DrawCircle() {
context.clearRect(0, 0, canvas.width, canvas.height);
circles.forEach(function(c) {
c.x += c.speed.x;
c.y += c.speed.y;
context.beginPath();
context.arc(c.x, c.y, c.r, 0, 2 * Math.PI);
context.fillStyle = c.color;
context.fill();
if (c.x + c.r < 0) c.x = canvas.width + c.r
if (c.y + c.r < 0) c.y = canvas.height + c.r
});
window.requestAnimationFrame(DrawCircle);
}
DrawCircle();
<canvas id="canvas"></canvas>
But if you are going to do a lot more animations you should consider using a game engine, there are a lot of great open source ones:
https://github.com/collections/javascript-game-engines
Since you're getting a sequence of circles, it looks like you're not clearing the canvas when a frame is drawn. Simply draw a white rectangle that fills the canvas whenever a new frame is requested, then draw your circle.
The method you provide as an argument to requestAnimationFrame is responsible for drawing a complete image on the canvas which replaces whatever was there during the previous frame.

Convert squares to circles in canvas html

OK so I appreciate that this is a massively basic question but I'm totally new to canvas and I just need to do something simple. Basically I am using springy.js to draw force directed graphs. The nodes on the graph are squares and I just want them to be circles. Can someone show me what I should change in the code below and I can figure out the rest from there
I tried
ctx.arc(s.x - boxWidth/2, s.y - boxHeight/2, boxWidth, boxHeight,2*Math.PI);
ctx.stroke();
instead of the line with clearRect but the boxes remain and the connections between boxes stop being straight lines.
function drawNode(node, p) {
var s = toScreen(p);
ctx.save();
// Pulled out the padding aspect sso that the size functions could be used in multiple places
// These should probably be settable by the user (and scoped higher) but this suffices for now
var paddingX = 6;
var paddingY = 6;
var contentWidth = node.getWidth();
var contentHeight = node.getHeight();
var boxWidth = contentWidth + paddingX;
var boxHeight = contentHeight + paddingY;
// clear background
ctx.clearRect(s.x - boxWidth/2, s.y - boxHeight/2, boxWidth, boxHeight);
// fill background
if (selected !== null && selected.node !== null && selected.node.id === node.id) {
ctx.fillStyle = "#FFFFE0"; //when clicked
} else if (nearest !== null && nearest.node !== null && nearest.node.id === node.id) {
ctx.fillStyle = "#EEEEEE";//when hovered over
} else {
//if the node.FBScore >10 then ctx.fillStyle = "#F00909";
ctx.fillStyle = "#E34747";//normal colour
}
ctx.fillRect(s.x - boxWidth/2, s.y - boxHeight/2, boxWidth, boxHeight);
if (node.data.image == undefined) {
ctx.textAlign = "left";
ctx.textBaseline = "top";
ctx.font = (node.data.font !== undefined) ? node.data.font : nodeFont;
ctx.fillStyle = (node.data.color !== undefined) ? node.data.color : "#000000";
var text = (node.data.label !== undefined) ? node.data.label : node.id;
ctx.fillText(text, s.x - contentWidth/2, s.y - contentHeight/2);
} else {
// Currently we just ignore any labels if the image object is set. One might want to extend this logic to allow for both, or other composite nodes.
var src = node.data.image.src; // There should probably be a sanity check here too, but un-src-ed images aren't exaclty a disaster.
if (src in nodeImages) {
if (nodeImages[src].loaded) {
// Our image is loaded, so it's safe to draw
ctx.drawImage(nodeImages[src].object, s.x - contentWidth/2, s.y - contentHeight/2, contentWidth, contentHeight);
}
}else{
// First time seeing an image with this src address, so add it to our set of image objects
// Note: we index images by their src to avoid making too many duplicates
nodeImages[src] = {};
var img = new Image();
nodeImages[src].object = img;
img.addEventListener("load", function () {
// HTMLImageElement objects are very finicky about being used before they are loaded, so we set a flag when it is done
nodeImages[src].loaded = true;
});
img.src = src;
}
}
ctx.restore();
}
Instead of replacing the clearRect() method, you should replace the fillRect() with the arc(x, y, radius, startAngle, endAngle, anticlockwise); one :
var canvas = document.querySelector('canvas');
var ctx = canvas.getContext('2d');
function drawNode() {
var s = {
x: (Math.random() * 200) + 50,
y: (Math.random() * 200) + 50
};
var paddingX = 6;
var paddingY = 6;
var contentWidth = s.x / 5;
var contentHeight = s.y / 5;
var boxWidth = contentWidth + paddingX;
var boxHeight = contentHeight + paddingY;
ctx.fillStyle = '#AAFFAA';
// I modified it so the whole canvas will be cleared
ctx.clearRect(0, 0, canvas.width, canvas.height);
// We start a new path
ctx.beginPath();
// then we draw our circle, setting its radius to the max between contentWidth and contentHeight
ctx.arc(s.x, s.y , Math.max(boxWidth,boxHeight)/2, 0, 2 * Math.PI);
// and finally we fill it
ctx.fill();
}
document.addEventListener('click', drawNode);
drawNode();
canvas{cursor:pointer};
<canvas height="300" width="300" />
I have create a simple jsFiddle to show how to draw a curved corner rectangle
-- Updated so you can now simply change the roundedValue which will then change the smoothness of the corners.
http://jsfiddle.net/gHCJt/1127/
// Get canvas
var canvas = $("#canvas");
var context = canvas.get(0).getContext("2d");
// Draw simple rect
var rectX = 125;
var rectY = 125;
var rectWidth = 150;
var rectHeight = 150;
var roundedValue = 75;
// Apply corner
context.lineJoin = "round";
context.lineWidth = roundedValue;
// Apply the corner to the draw method for strokeRect and fillRect
context.strokeRect(rectX+(roundedValue/2), rectY+(roundedValue/2), rectWidth-roundedValue, rectHeight-roundedValue);
context.fillRect(rectX+(roundedValue/2), rectY+(roundedValue/2), rectWidth-roundedValue, rectHeight-roundedValue);

Categories

Resources