This is the effect that I am trying to achieve: link
I have gotten the four waves and they are indeed animated, but I have gotten stuck on giving each of them a slightly different animation. At the current point, all curves move at the same speed, in the same direction and switch at the same place too. they should vary slightly in all these aspects. The end result I am looking for is very much like the link i posted, with difference that each wave can only have a maximum of one cycle, that is going up once and coming down once. Thank you for your input.
Below is my code:
function start() {
var canvas = $("canvas");
console.log(canvas);
canvas.each(function(index, canvas) {
var context = canvas.getContext("2d");
canvas.width = $(".box").eq(index).width();
canvas.height = $(".box").eq(index).height();
context.clearRect(0, 0, canvas.width, canvas.height);
drawCurves(context, step);
step += 1;
});
requestAnimationFrame(start);
}
var step = -1;
function drawCurves(ctx, step) {
var width = ctx.canvas.width;
var height = ctx.canvas.height;
ctx.lineWidth = 2;
for (i = 0; i < 4 ; i++) {
var x = 0;
var y = 0;
ctx.beginPath();
if (i === 0 ) {
ctx.strokeStyle = "red";
var amplitude = 20;
var frequency = height / (2 * Math.PI) ;
console.log(i, frequency);
} if ( i === 1) {
ctx.strokeStyle = "blue";
var amplitude = 30;
var frequency = (height / (2 * Math.PI));
console.log(i, frequency);
} if ( i === 2) {
ctx.strokeStyle = "green";
var amplitude = 40;
var frequency = height / (2 * Math.PI) ;
console.log(i, frequency);
} if (i === 3) {
ctx.strokeStyle = "yellow";
var amplitude = 50;
var frequency = height / (2 * Math.PI) ;
console.log(i, frequency);
}
ctx.save();
ctx.translate(-amplitude * Math.sin(step / frequency), 0);
while (y < height) {
x = (width / 2) + (amplitude * Math.sin((y + step) / frequency)) ;
ctx.lineTo(x, y);
y++;
}
ctx.closePath();
ctx.stroke();
ctx.restore();
}
}
$(document).ready(function() {
start();
})
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div class="box">
<canvas id="canvas"></canvas>
</div>
<div class="box">
<canvas id="canvas"></canvas>
</div>
</body>
</html>
And here a Code Pen
Your code draw only one sinus wave.
I'll advice you those points:
_If you want different(simultaneaous) wave you've to use differents x/y values at the draw point.
_You use $(document).ready(function() as animation loop, that's not the better way do do it. For animation you should set a setInterval or way better use the requestAnimationFrame who is meant to create animation. In each animation loop draw the 4 sinus lines, i'll forget about step for using objects instead that i think is better but that's not important point. I've no time to try your code but what is it doing when using requestAnimationFrame(start()) instead of the $document.ready ?
Obviously in each animatve to clear the drawing place using clearRect(width,height); for example.
_The 4 steps add +1 to +4 to y in the same cartesian equation. In a sinus curve that will not really be seeing by human eyes because it's a really slight variation. You can use differents equations using differents sinusoïd equations for each step/object, i.e. Math.sin(y)+10 or Math.sin(y)*10 etc...or even different incremantation value for each differents objects.
_i'll avoid translate and use a for loop to increment the x value and get the y value using sin(x) and whatever equation you need , that's personal choice but will avoid to write the .translate() line and will use normal coordinates instead of moved coordinates so you'll easily see in console.log real coordinates in canvas area.
_As for previous you can throw away the checking IFs at beginning of program if you use objets in a array (and loop it) and set a styleColor and weight of each of 4 objets.
Hope it's help, no time to write a clean code.
#enxaneta thank you for your input! Got it the way I wanted to, below is my solution:
var step = 0;
function start(timestamp) {
var canvas = $("canvas");
canvas.each(function(index, canvas) {
var context = canvas.getContext("2d");
canvas.width = $(".box").eq(index).width();
canvas.height = $(".box").eq(index).height();
context.clearRect(0, 0, canvas.width, canvas.height);
if (canvas.height > 1000 ) {
drawWave(context, 20,"sin");
drawWave(context, 60,"cos");
drawWave(context, 40,"sin");
drawWave(context, 80,"cos");
}
if (canvas.height < 1000 ) {
drawWave(context, 10,"sin");
drawWave(context, 30,"cos");
drawWave(context, 20,"sin");
drawWave(context, 40,"cos");
}
step = timestamp / 7;
});
window.requestAnimationFrame(start);
}
function drawWave(ctx,amplitude,trig){
var width = ctx.canvas.width;
var height = ctx.canvas.height;
ctx.beginPath();
ctx.lineWidth = 2;
ctx.strokeStyle = "blue";
var x = 0;
var y = 0;
var frequency = height / (2 * Math.PI);
ctx.save();
ctx.translate(-amplitude * Math[trig](step / frequency), 0);
while (y < height) {
x = width / 2 + amplitude * Math[trig]((y + step) / frequency);
ctx.lineTo(x, y);
y++;
}
// ctx.closePath();
ctx.stroke();
ctx.restore();
}
$(document).ready(function() {
start();
});
canvas {
background-color: wheat;
position: absolute;
}
.box {
width: 500px;
height: 2000px;
border: solid;
}
.box.t {
width: 500px;
height: 200px;
border: solid;
}
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<div class="box t">
<canvas id="canvas"></canvas>
</div>
<div class="box">
<canvas id="canvas"></canvas>
</div>
</body>
</html>
Related
I am visualising flight paths with D3 and Canvas. In short, I have data for each flight's origin and destination
as well as the airport coordinates. The ideal end state is to have an indiviudal circle representing a plane moving
along each flight path from origin to destination. The current state is that each circle gets visualised along the path,
yet the removal of the previous circle along the line does not work as clearRect gets called nearly constantly.
Current state:
Ideal state (achieved with SVG):
The Concept
Conceptually, an SVG path for each flight is produced in memory using D3's custom interpolation with path.getTotalLength() and path.getPointAtLength() to move the circle along the path.
The interpolator returns the points along the path at any given time of the transition. A simple drawing function takes these points and draws the circle.
Key functions
The visualisation gets kicked off with:
od_pairs.forEach(function(el, i) {
fly(el[0], el[1]); // for example: fly('LHR', 'JFK')
});
The fly() function creates the SVG path in memory and a D3 selection of a circle (the 'plane') - also in memory.
function fly(origin, destination) {
var pathElement = document.createElementNS(d3.namespaces.svg, 'path');
var routeInMemory = d3.select(pathElement)
.datum({
type: 'LineString',
coordinates: [airportMap[origin], airportMap[destination]]
})
.attr('d', path);
var plane = custom.append('plane');
transition(plane, routeInMemory.node());
}
The plane gets transitioned along the path by the custom interpolater in the delta() function:
function transition(plane, route) {
var l = route.getTotalLength();
plane.transition()
.duration(l * 50)
.attrTween('pointCoordinates', delta(plane, route))
// .on('end', function() { transition(plane, route); });
}
function delta(plane, path) {
var l = path.getTotalLength();
return function(i) {
return function(t) {
var p = path.getPointAtLength(t * l);
draw([p.x, p.y]);
};
};
}
... which calls the simple draw() function
function draw(coords) {
// contextPlane.clearRect(0, 0, width, height); << how to tame this?
contextPlane.beginPath();
contextPlane.arc(coords[0], coords[1], 1, 0, 2*Math.PI);
contextPlane.fillStyle = 'tomato';
contextPlane.fill();
}
This results in an extending 'path' of circles as the circles get drawn yet not removed as shown in the first gif above.
Full code here: http://blockbuilder.org/larsvers/8e25c39921ca746df0c8995cce20d1a6
My question is, how can I achieve to draw only a single, current circle while the previous circle gets removed without interrupting other circles being drawn on the same canvas?
Some failed attempts:
The natural answer is of course context.clearRect(), however, as there's a time delay (roughly a milisecond+) for each circle to be drawn as it needs to get through the function pipeline clearRect gets fired almost constantly.
I tried to tame the perpetual clearing of the canvas by calling clearRect only at certain intervals (Date.now() % 10 === 0 or the like) but that leads to no good either.
Another thought was to calculate the previous circle's position and remove the area specifically with a small and specific clearRect definition within each draw() function.
Any pointers very much appreciated.
Handling small dirty regions, especially if there is overlap between objects quickly becomes very computationally heavy.
As a general rule, a average Laptop/desktop can easily handle 800 animated objects if the computation to calculate position is simple.
This means that the simple way to animate is to clear the canvas and redraw every frame. Saves a lot of complex code that offers no advantage over the simple clear and redraw.
const doFor = (count,callback) => {var i=0;while(i < count){callback(i++)}};
function createIcon(drawFunc){
const icon = document.createElement("canvas");
icon.width = icon.height = 10;
drawFunc(icon.getContext("2d"));
return icon;
}
function drawPlane(ctx){
const cx = ctx.canvas.width / 2;
const cy = ctx.canvas.height / 2;
ctx.beginPath();
ctx.strokeStyle = ctx.fillStyle = "red";
ctx.lineWidth = cx / 2;
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.moveTo(cx/2,cy)
ctx.lineTo(cx * 1.5,cy);
ctx.moveTo(cx,cy/2)
ctx.lineTo(cx,cy*1.5)
ctx.stroke();
ctx.lineWidth = cx / 4;
ctx.moveTo(cx * 1.7,cy * 0.6)
ctx.lineTo(cx * 1.7,cy*1.4)
ctx.stroke();
}
const planes = {
items : [],
icon : createIcon(drawPlane),
clear(){
planes.items.length = 0;
},
add(x,y){
planes.items.push({
x,y,
ax : 0, // the direction of the x axis of this plane
ay : 0,
dir : Math.random() * Math.PI * 2,
speed : Math.random() * 0.2 + 0.1,
dirV : (Math.random() - 0.5) * 0.01, // change in direction
})
},
update(){
var i,p;
for(i = 0; i < planes.items.length; i ++){
p = planes.items[i];
p.dir += p.dirV;
p.ax = Math.cos(p.dir);
p.ay = Math.sin(p.dir);
p.x += p.ax * p.speed;
p.y += p.ay * p.speed;
}
},
draw(){
var i,p;
const w = canvas.width;
const h = canvas.height;
for(i = 0; i < planes.items.length; i ++){
p = planes.items[i];
var x = ((p.x % w) + w) % w;
var y = ((p.y % h) + h) % h;
ctx.setTransform(-p.ax,-p.ay,p.ay,-p.ax,x,y);
ctx.drawImage(planes.icon,-planes.icon.width / 2,-planes.icon.height / 2);
}
}
}
const ctx = canvas.getContext("2d");
function mainLoop(){
if(canvas.width !== innerWidth || canvas.height !== innerHeight){
canvas.width = innerWidth;
canvas.height = innerHeight;
planes.clear();
doFor(800,()=>{ planes.add(Math.random() * canvas.width, Math.random() * canvas.height) })
}
ctx.setTransform(1,0,0,1,0,0);
// clear or render a background map
ctx.clearRect(0,0,canvas.width,canvas.height);
planes.update();
planes.draw();
requestAnimationFrame(mainLoop)
}
requestAnimationFrame(mainLoop)
canvas {
position : absolute;
top : 0px;
left : 0px;
}
<canvas id=canvas></canvas>
800 animated points
As pointed out in the comments some machines may be able to draw a circle if one colour and all as one path slightly quicker (not all machines). The point of rendering an image is that it is invariant to the image complexity. Image rendering is dependent on the image size but colour and alpha setting per pixel have no effect on rendering speed. Thus I have changed the circle to show the direction of each point via a little plane icon.
Path follow example
I have added a way point object to each plane that in the demo has a random set of way points added. I called it path (could have used a better name) and a unique path is created for each plane.
The demo is to just show how you can incorporate the D3.js interpolation into the plane update function. The plane.update now calls the path.getPos(time) which returns true if the plane has arrived. If so the plane is remove. Else the new plane coordinates are used (stored in the path object for that plane) to set the position and direction.
Warning the code for path does little to no vetting and thus can easily be made to throw an error. It is assumed that you write the path interface to the D3.js functionality you want.
const doFor = (count,callback) => {var i=0;while(i < count){callback(i++)}};
function createIcon(drawFunc){
const icon = document.createElement("canvas");
icon.width = icon.height = 10;
drawFunc(icon.getContext("2d"));
return icon;
}
function drawPlane(ctx){
const cx = ctx.canvas.width / 2;
const cy = ctx.canvas.height / 2;
ctx.beginPath();
ctx.strokeStyle = ctx.fillStyle = "red";
ctx.lineWidth = cx / 2;
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.moveTo(cx/2,cy)
ctx.lineTo(cx * 1.5,cy);
ctx.moveTo(cx,cy/2)
ctx.lineTo(cx,cy*1.5)
ctx.stroke();
ctx.lineWidth = cx / 4;
ctx.moveTo(cx * 1.7,cy * 0.6)
ctx.lineTo(cx * 1.7,cy*1.4)
ctx.stroke();
}
const path = {
wayPoints : null, // holds way points
nextTarget : null, // holds next target waypoint
current : null, // hold previously passed way point
x : 0, // current pos x
y : 0, // current pos y
addWayPoint(x,y,time){
this.wayPoints.push({x,y,time});
},
start(){
if(this.wayPoints.length > 1){
this.current = this.wayPoints.shift();
this.nextTarget = this.wayPoints.shift();
}
},
getNextTarget(){
this.current = this.nextTarget;
if(this.wayPoints.length === 0){ // no more way points
return;
}
this.nextTarget = this.wayPoints.shift(); // get the next target
},
getPos(time){
while(this.nextTarget.time < time && this.wayPoints.length > 0){
this.getNextTarget(); // get targets untill the next target is ahead in time
}
if(this.nextTarget.time < time){
return true; // has arrivecd at target
}
// get time normalised ove time between current and next
var timeN = (time - this.current.time) / (this.nextTarget.time - this.current.time);
this.x = timeN * (this.nextTarget.x - this.current.x) + this.current.x;
this.y = timeN * (this.nextTarget.y - this.current.y) + this.current.y;
return false; // has not arrived
}
}
const planes = {
items : [],
icon : createIcon(drawPlane),
clear(){
planes.items.length = 0;
},
add(x,y){
var p;
planes.items.push(p = {
x,y,
ax : 0, // the direction of the x axis of this plane
ay : 0,
path : Object.assign({},path,{wayPoints : []}),
})
return p; // return the plane
},
update(time){
var i,p;
for(i = 0; i < planes.items.length; i ++){
p = planes.items[i];
if(p.path.getPos(time)){ // target reached
planes.items.splice(i--,1); // remove
}else{
p.dir = Math.atan2(p.y - p.path.y, p.x - p.path.x) + Math.PI; // add 180 because i drew plane wrong way around.
p.ax = Math.cos(p.dir);
p.ay = Math.sin(p.dir);
p.x = p.path.x;
p.y = p.path.y;
}
}
},
draw(){
var i,p;
const w = canvas.width;
const h = canvas.height;
for(i = 0; i < planes.items.length; i ++){
p = planes.items[i];
var x = ((p.x % w) + w) % w;
var y = ((p.y % h) + h) % h;
ctx.setTransform(-p.ax,-p.ay,p.ay,-p.ax,x,y);
ctx.drawImage(planes.icon,-planes.icon.width / 2,-planes.icon.height / 2);
}
}
}
const ctx = canvas.getContext("2d");
function mainLoop(time){
if(canvas.width !== innerWidth || canvas.height !== innerHeight){
canvas.width = innerWidth;
canvas.height = innerHeight;
planes.clear();
doFor(810,()=>{
var p = planes.add(Math.random() * canvas.width, Math.random() * canvas.height);
// now add random number of way points
var timeP = time;
// info to create a random path
var dir = Math.random() * Math.PI * 2;
var x = p.x;
var y = p.y;
doFor(Math.floor(Math.random() * 80 + 12),()=>{
var dist = Math.random() * 5 + 4;
x += Math.cos(dir) * dist;
y += Math.sin(dir) * dist;
dir += (Math.random()-0.5)*0.3;
timeP += Math.random() * 1000 + 500;
p.path.addWayPoint(x,y,timeP);
});
// last waypoin at center of canvas.
p.path.addWayPoint(canvas.width / 2,canvas.height / 2,timeP + 5000);
p.path.start();
})
}
ctx.setTransform(1,0,0,1,0,0);
// clear or render a background map
ctx.clearRect(0,0,canvas.width,canvas.height);
planes.update(time);
planes.draw();
requestAnimationFrame(mainLoop)
}
requestAnimationFrame(mainLoop)
canvas {
position : absolute;
top : 0px;
left : 0px;
}
<canvas id=canvas></canvas>
800 animated points
#Blindman67 is correct, clear and redraw everything, every frame.
I'm here just to say that when dealing with such primitive shapes as arc without too many color variations, it's actually better to use the arc method than drawImage().
The idea is to wrap all your shapes in a single path declaration, using
ctx.beginPath(); // start path declaration
for(i; i<shapes.length; i++){ // loop through our points
ctx.moveTo(pt.x + pt.radius, pt.y); // default is lineTo and we don't want it
// Note the '+ radius', arc starts at 3 o'clock
ctx.arc(pt.x, pt.y, pt.radius, 0, Math.PI*2);
}
ctx.fill(); // a single fill()
This is faster than drawImage, but the main caveat is that it works only for single-colored set of shapes.
I've made an complex plotting app, where I do draw a lot (20K+) of entities, with animated positions. So what I do, is to store two sets of points, one un-sorted (actually sorted by radius), and one
sorted by color. I then do use the sorted-by-color one in my animations loop, and when the animation is complete, I draw only the final frame with the sorted-by-radius (after I filtered the non visible entities). I achieve 60fps on most devices. When I tried with drawImage, I was stuck at about 10fps for 5K points.
Here is a modified version of Blindman67's good answer's snippet, using this single-path approach.
/* All credits to SO user Blindman67 */
const doFor = (count,callback) => {var i=0;while(i < count){callback(i++)}};
const planes = {
items : [],
clear(){
planes.items.length = 0;
},
add(x,y){
planes.items.push({
x,y,
rad: 2,
dir : Math.random() * Math.PI * 2,
speed : Math.random() * 0.2 + 0.1,
dirV : (Math.random() - 0.5) * 0.01, // change in direction
})
},
update(){
var i,p;
for(i = 0; i < planes.items.length; i ++){
p = planes.items[i];
p.dir += p.dirV;
p.x += Math.cos(p.dir) * p.speed;
p.y += Math.sin(p.dir) * p.speed;
}
},
draw(){
var i,p;
const w = canvas.width;
const h = canvas.height;
ctx.beginPath();
ctx.fillStyle = 'red';
for(i = 0; i < planes.items.length; i ++){
p = planes.items[i];
var x = ((p.x % w) + w) % w;
var y = ((p.y % h) + h) % h;
ctx.moveTo(x + p.rad, y)
ctx.arc(x, y, p.rad, 0, Math.PI*2);
}
ctx.fill();
}
}
const ctx = canvas.getContext("2d");
function mainLoop(){
if(canvas.width !== innerWidth || canvas.height !== innerHeight){
canvas.width = innerWidth;
canvas.height = innerHeight;
planes.clear();
doFor(8000,()=>{ planes.add(Math.random() * canvas.width, Math.random() * canvas.height) })
}
ctx.setTransform(1,0,0,1,0,0);
// clear or render a background map
ctx.clearRect(0,0,canvas.width,canvas.height);
planes.update();
planes.draw();
requestAnimationFrame(mainLoop)
}
requestAnimationFrame(mainLoop)
canvas {
position : absolute;
top : 0px;
left : 0px;
z-index: -1;
}
<canvas id=canvas></canvas>
8000 animated points
Not directly related but in case you've got part of your drawings that don't update at the same rate as the rest (e.g if you want to highlight an area of your map...) then you might also consider separating your drawings in different layers, on offscreen canvases. This way you'd have one canvas for the planes, that you'd clear every frame, and other canvas for other layers that you would update at different rate. But that's an other story.
I'm trying to resize a rotated shape on canvas. My problem is that when I call the rendering function, the shape starts "drifting" depending on the shape angle. How can I prevent this?
I've made a simplified fiddle demonstrating the problem, when the canvas is clicked, the shape is grown and for some reason it drifts upwards.
Here's the fiddle: https://jsfiddle.net/x5gxo1p7/
<style>
canvas {
position: absolute;
box-sizing: border-box;
border: 1px solid red;
}
</style>
<body>
<div>
<canvas id="canvas"></canvas>
</div>
</body>
<script type="text/javascript">
var canvas = document.getElementById('canvas');
canvas.width = 300;
canvas.height= 150;
var ctx = canvas.getContext('2d');
var counter = 0;
var shape = {
top: 120,
left: 120,
width: 120,
height: 60,
rotation: Math.PI / 180 * 15
};
function draw() {
var h2 = shape.height / 2;
var w2 = shape.width / 2;
var x = w2;
var y = h2;
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(75,37.5)
ctx.translate(x, y);
ctx.rotate(Math.PI / 180 * 15);
ctx.translate(-x, -y);
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, shape.width, shape.height);
ctx.restore();
}
canvas.addEventListener('click', function() {
shape.width = shape.width + 15;
window.requestAnimationFrame(draw.bind(this));
});
window.requestAnimationFrame(draw.bind(this));
</script>
In the "real" code the shape is resized when the resize-handle is clicked and moved but I think this example demonstrates the problem sufficiently.
EDIT: updated fiddle to clarify the issue:
https://jsfiddle.net/x5gxo1p7/9/
Always use local coordinates to define shapes.
When rendering content that is intended to be transformed the content should be in its own (local) coordinate system. Think of a image. the top left pixel is always at 0,0 on the image no matter where you render it. The pixels are at their local coordinates, when rendered they are moved to the (world) canvas coordinates via the current transformation.
So if you make your shape with coordinates set to its local, making the rotation point at its local origin (0,0) the display coordinates are stored separately as world coordinates
var shape = {
top: -30, // local coordinates with rotation origin
left: -60, // at 0,0
width: 120,
height: 60,
world : {
x : canvas.width / 2,
y : canvas.height / 2,
rot : Math.PI / 12, // 15deg clockwise
}
};
Now you don't have to mess about with translating forward and back... blah blah total pain.
Just
ctx.save();
ctx.translate(shape.world.x,shape.world.y);
ctx.rotate(shape.world.rot);
ctx.fillRect(shape.left, shape.top, shape.width, shape.height)
ctx.restore();
or event quicker and eliminating the need to use save and restore
ctx.setTransform(1,0,0,1,shape.world.x,shape.world.y);
ctx.rotate(shape.world.rot);
ctx.fillRect(shape.left, shape.top, shape.width, shape.height);
The local shape origin (0,0) is where the transformation places the translation.
This greatly simplifies a lot of the work that has to be done
var canvas = document.getElementById('canvas');
canvas.width = 300;
canvas.height= 150;
var ctx = canvas.getContext('2d');
ctx.fillStyle = "black";
ctx.strokeStyle = "red";
ctx.lineWidth = 2;
var shape = {
top: -30, // local coordinates with rotation origin
left: -60, // at 0,0
width: 120,
height: 60,
world : {
x : canvas.width / 2,
y : canvas.height / 2,
rot : Math.PI / 12, // 15deg clockwise
}
};
function draw() {
ctx.setTransform(1,0,0,1,0,0); // to clear use default transform
ctx.clearRect(0, 0, canvas.width, canvas.height);
// you were scaling the shape, that can be done via a transform
// once you have moved the shape to the world coordinates.
ctx.setTransform(1,0,0,1,shape.world.x,shape.world.y);
ctx.rotate(shape.world.rot);
// after the transformations have moved the local to the world
// you can ignore the canvas coordinates and work within the objects
// local. In this case showing the unscaled box
ctx.strokeRect(shape.left, shape.top, shape.width, shape.height);
// and a line above the box
ctx.strokeRect(shape.left, shape.top - 5, shape.width, 1);
ctx.scale(0.5,0.5); // the scaling you were doing
ctx.fillRect(shape.left, shape.top, shape.width, shape.height);
}
canvas.addEventListener('click', function() {
shape.width += 15;
shape.left -= 15 / 2;
shape.world.rot += Math.PI / 45; // rotate to illustrate location
// of local origin
var distToMove = 15/2;
shape.world.x += Math.cos(shape.world.rot) * distToMove;
shape.world.y += Math.sin(shape.world.rot) * distToMove;
draw();
});
// no need to use requestAnimationFrame (RAF) if you are not animation
// but its not wrong. Nor do you need to bind this (in this case
// this = window) to the callback RAF does not bind a context
// to the callback
/*window.requestAnimationFrame(draw.bind(this));*/
requestAnimationFrame(draw); // functionaly identical
// or just
/*draw()*/ //will work
body { font-family : Arial,"Helvetica Neue",Helvetica,sans-serif; font-size : 12px; color : #242729;} /* SO font currently being used */
canvas { border: 1px solid red; }
<canvas id="canvas"></canvas>
<p>Click to grow "and rotate" (I add that to illustrate the local origin)</p>
<p>I have added a red box and a line above the box, showing how using the local coordinates to define a shape makes it a lot easier to then manipulate that shape when rendering "see code comments".</p>
Try this. You had ctx.translate() used where it was not entirely necessary. That caused the problems.
<script type="text/javascript">
var canvas = document.getElementById('canvas');
canvas.width = 300;
canvas.height= 150;
var ctx = canvas.getContext('2d');
var counter = 0;
var shape = {
top: 120,
left: 120,
width: 120,
height: 60,
rotation: Math.PI / 180 * 15
};
function draw() {
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(75,37.5)
ctx.rotate(Math.PI / 180 * 15);
ctx.fillStyle = '#000';
ctx.fillRect(0, 0, shape.width, shape.height);
ctx.restore();
}
canvas.addEventListener('click', function() {
shape.width = shape.width + 15;
window.requestAnimationFrame(draw.bind(this));
});
window.requestAnimationFrame(draw.bind(this));
</script>
This is happening because the x and y are set as the half value of the shape size, which completely changes its position.
You should set a point for the center of the shape, anyway. I set this point as ctx.canvas.[width or height] / 2, the half of the canvas.
var h2 = shape.height / 2;
var w2 = shape.width / 2;
var x = (ctx.canvas.width / 2) - w2;
var y = (ctx.canvas.height / 2) - h2;
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(x + (shape.width / 2), y + (shape.height / 2));
ctx.rotate(((shape.rotation * Math.PI) / 180) * 15);
ctx.fillStyle = '#000';
ctx.fillRect(-shape.width / 2, -shape.height / 2, shape.width, shape.height);
ctx.restore();
Fiddle.
Found a solution, problem was that I wasn't calculating the new center point coordinates.
The new fiddle with solution: https://jsfiddle.net/HTxGb/151/
var canvas = document.getElementById('canvas');
var ctx = canvas.getContext('2d');
canvas.width =500;
canvas.height = 500;
var x = canvas.width/2;
var y = canvas.height/2;
var rectw = 20;
var recth = 20;
var rectx = -rectw/2;
var recty = -recth/2;
var rotation = 0;
var addedRotation = Math.PI/12;
var addedWidth = 20;
var addedHeight = 10;
var draw = function() {
ctx.save();
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.translate(x, y);
ctx.rotate(rotation);
ctx.fillRect(rectx, recty, rectw, recth);
ctx.restore();
}
document.getElementById('growXRight').addEventListener('click', function() {
rectx -= addedWidth/2;
x += addedWidth/2 * Math.cos(rotation);
y -= addedWidth/2 * Math.sin(-rotation);
rectw += addedWidth;
draw();
})
document.getElementById('growXLeft').addEventListener('click', function() {
rectx -= addedWidth/2;
x -= addedWidth/2 * Math.cos(rotation);
y += addedWidth/2 * Math.sin(-rotation);
rectw += addedWidth;
draw();
})
document.getElementById('growYTop').addEventListener('click', function() {
recty -= addedHeight/2;
x += addedHeight/2 * Math.sin(rotation);
y -= addedHeight/2 * Math.cos(-rotation);
recth += addedHeight;
draw();
})
document.getElementById('growYBottom').addEventListener('click', function() {
recty -= addedHeight/2;
x -= addedHeight/2 * Math.sin(rotation);
y += addedHeight/2 * Math.cos(-rotation);
recth += addedHeight;
draw();
})
document.getElementById('rotatePlus').addEventListener('click', function() {
rotation += addedRotation;
rotation = rotation % (Math.PI*2);
if(rotation % Math.PI*2 < 0) {
rotation += Math.PI*2;
}
draw();
})
document.getElementById('rotateMinus').addEventListener('click', function() {
rotation -= addedRotation;
rotation = rotation % (Math.PI*2);
if(rotation % Math.PI*2 < 0) {
rotation += Math.PI*2;
}
draw();
})
draw();
I am trying to create a game (at beginning stages). I am trying to create balls that bounce around a canvas, I have created balls, randomized them and have animated them.
But when trying to add a boundary I can only seem to get the balls to act as one object rather than separate ones. If NumShapes is changed to 1 it works perfectly.
if( shapes[i].x<0 || shapes[i].x>width) dx=-dx;
if( shapes[i].y<0 || shapes[i].y>height) dy=-dy;
For movement:
shapes[i].x+=dx;
shapes[i].y+=dy;
See this:
var ctx;
var numShapes;
var shapes;
var dx = 5; // speed on the X axis
var dy = 5; // speed on the Y axis
var ctx = canvas.getContext('2d');
var width = canvas.width;
var height = canvas.height;
function init() // draws on the Canvas in the HTML
// calling functions here would not run them in the setInterval
{
numShapes = 10;
shapes = [];
drawScreen();
ctx = canvas.getContext('2d');
setInterval(draw, 10); // Runs the Draw function with nestled functions
makeShapes();
}
function draw() {
clear();
drawShapes();
}
function clear() {
ctx.clearRect(0, 0, width, height); // clears the canvas by WIDTH and HEIGHT variables
}
function makeShapes() {
var i;
var tempRad;
var tempR;
var tempG;
var tempB;
var tempX;
var tempY;
var tempColor;
for (i = 0; i < numShapes; i++) { // runs while i is less than numShapes
tempRad = 10 + Math.floor(Math.random() * 25); // random radius number
tempX = Math.random() * (width - tempRad); // random X value
tempY = Math.random() * (height - tempRad); // random Y value
tempR = Math.floor(Math.random() * 255); // random red value
tempG = Math.floor(Math.random() * 255); // random green value
tempB = Math.floor(Math.random() * 255); // random blue value
tempColor = "rgb(" + tempR + "," + tempG + "," + tempB + ")"; // creates a random colour
tempShape = {
x: tempX,
y: tempY,
rad: tempRad,
color: tempColor
}; // creates a random shape based on X, Y and R
shapes.push(tempShape); // pushes the shape into the array
}
}
function drawShapes() {
var i;
for (i = 0; i < numShapes; i++) {
ctx.fillStyle = shapes[i].color;
ctx.beginPath();
ctx.arc(shapes[i].x, shapes[i].y, shapes[i].rad, 0, 2 * Math.PI, false);
ctx.closePath();
ctx.fill();
shapes[i].x += dx; // increases the X value of Shape
shapes[i].y += dy; // increases the Y value of Shape
// Boundary, but applies to all shapes as one shape
if (shapes[i].x < 0 || shapes[i].x > width) dx = -dx;
if (shapes[i].y < 0 || shapes[i].y > height) dy = -dy;
}
}
function drawScreen() {
//bg
ctx.fillStyle = '#EEEEEE';
ctx.fillRect(0, 0, width, height);
//Box
ctx.strokeStyle = '#000000';
ctx.strokeRect(1, 1, width - 2, height - 2);
}
canvas {
border: 1px solid #333;
}
<body onLoad="init();">
<div class="container container-main">
<div class="container-canvas">
<canvas id="canvas" width="800" height="600">
This is my fallback content.
</canvas>
</div>
</div>
</body>
Your dx and dy are globals, they should be unique for each ball object that you are simulating. Either clear them to 0 in your rendering loop (draw) or actually implement a ball object/class to hold variables unique to that object.
When you do your collision detection you change dx and dy which then persists to the next ball object as they are global.
Your fiddle, edited to add local dx and dy per shape: https://jsfiddle.net/a9b3rm5u/3/
tempDx = Math.random()*5; // random DX value
tempDy = Math.random()*5; // random DY value
shapes[i].x+=shapes[i].dx;// increases the X value of Shape
shapes[i].y+=shapes[i].dy;// increases the Y value of Shape
if( shapes[i].x<0 || shapes[i].x>width) shapes[i].dx= - shapes[i].dx;
if( shapes[i].y<0 || shapes[i].y>height) shapes[i].dy= -shapes[i].dy;
I've been working on a chunk of code for a would-be Sierpinski fractal animation, but for some reason, the animation part just doesn't seem to work. I also tried using setInterval(), with the same results, namely a blank canvas. The idea is to draw an equilateral triangle with vertex coordinates as parameters step by step, as though somebody was drawing it on a piece of paper. Could you have a look to see what's wrong with it?
On a side note, I've copied a few examples of canvas animation off a few web tutorials, and none of them appear to be working in my files either. I use Firefox and Chrome, both up to date, so I guess it's not a technical issue.
<!DOCTYPE html>
<style>
canvas {
width: 600px;
height: 600px;
text-align: center;
background-color: white;
background-position: center;
position: relative;
top: 0px;
left: 0px;
border:1px solid #d3d3d3;
}
<body>
<canvas id="sCanvas"></canvas>
<script>
This is where the animation is supposed to take place; draws a line from (Ax,Ay) to (Bx,By).
function lineAnimation(x1,y1,x2,y2,ctx) {
var deltaX = (x2 - x1) / 100;
var deltaY = (y2 - y1) / 100;
var x = x1;
var y = y1;
var timer = setInterval(function () {
ctx.moveTo(x,y);
ctx.lineTo(x+deltaX,y+deltaY);
ctx.stroke();
x += deltaX;
y += deltaY;
}, 100);
if ((x===x2) && (y===y2)) clearTimeout(timer);
}
function drawTriangle(Ax,Ay,Bx,By,Cx,Cy,ctx) {
lineAnimation(Ax,Ay,Bx,By,ctx);
lineAnimation(Bx,By,Cx,Cy,ctx);
lineAnimation(Cx,Cy,Ax,Ay,ctx);
}
function init() {
var canvas=document.getElementById("sCanvas");
var ctx = canvas.getContext("2d");
ctx.save();
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
drawTriangle(10,10,30,50,50,10);
}
init();
</script>
Your functions are requiring the parameter ctx which you didn't include, as such they don't know what ctx is. All you need to do is include it in drawTriangle():
drawTriangle(10,10,30,50,50,10,ctx);
And then everything works.
Try This
function lineAnimation(x1,y1,x2,y2,ctx) {
var deltaX = (x2 - x1) / 100;
var deltaY = (y2 - y1) / 100;
var x = x1;
var y = y1;
var timer = setInterval(function () {
var canvas=document.getElementById("sCanvas"); //Added Change
var ctx = canvas.getContext("2d"); //Added Change
ctx.moveTo(x,y);
ctx.lineTo(x+deltaX,y+deltaY);
ctx.stroke();
x += deltaX;
y += deltaY;
}, 100);
if ((x===x2) && (y===y2)) clearTimeout(timer);
}
function drawTriangle(Ax,Ay,Bx,By,Cx,Cy,ctx) {
lineAnimation(Ax,Ay,Bx,By,ctx);
lineAnimation(Bx,By,Cx,Cy,ctx);
lineAnimation(Cx,Cy,Ax,Ay,ctx);
}
function init() {
var canvas=document.getElementById("sCanvas");
var ctx = canvas.getContext("2d");
ctx.save();
ctx.clearRect(0,0,canvas.width,canvas.height);
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
drawTriangle(10,10,30,50,50,10);
}
init();
It is giving you an error over ctx.moveTo(x,y); and ctx.lineTo(x+deltaX,y+deltaY); because thet are not able to use you ctx which is basically an object of your canvas. so just try to add them in code and things will work fine
DEMO
Have what I think seems like a kind of strange problem.
I have a function that is made to draw an element in HTML5.
If i write it multiple times it is drawn those times, but if i place it in a loop it only draws the first time. Iv tried to monitor this by console.log for example but as soon as i try to draw this the loop is interrupted. It like there is some type of "break" function in it.
Anyone who has an idea about this?
<body>
<section id="wrapper">
<h1></h1>
<canvas id="canvas" width="800" height="600" style=" border-color: #000; border-style: solid; border-width: 1px;">
<p>Your browser doesn't support canvas.</p>
</canvas>
<script>
var context;
var canvas;
var WIDTH;
var HEIGHT;
$(document).ready(function() {
main_init();
});
function main_init() {
console.log("init");
WIDTH = $("#canvas").width();
HEIGHT = $("#canvas").height();
canvas = document.getElementById('canvas');
context = canvas.getContext('2d');
var width = 10;
var height = 10;
var posX = 30;
var posY = 60;
//NOT WORKING
for(y = 1; y < height; y+=1)
{
for(x = 1; x < width; x+=1)
{
console.log("y:"+ y + " x:" + x);
//console.log(isEven(x));
if(isEven(x))
{
HexagonObj(posX * x, posY * y, 0.95);
}
else
{
HexagonObj(posX * x, (posY + 20) * y, 0.95);
}
}
}
//WORKING
HexagonObj(-30, 60, 0.95);
HexagonObj(10, 80, 0.95);
HexagonObj(50, 60, 0.95);
HexagonObj(-30, 100, 0.95);
}
HexagonObj = function(xCorrd, yCorrd, size){
//console.log("hexagon");
var x0=xCorrd; var y0=yCorrd; //cordinates
var xx=20 * size; var yy=20 * size; //size of the legs of the shape
x=x0; y=y0; context.moveTo(x,y);
x+=xx; y+=0; context.moveTo(x,y);
x+=xx; y+=0; context.lineTo(x,y);
x+=xx; y+=yy; context.lineTo(x,y);
x+=(xx*-1); y+=yy; context.lineTo(x,y);
x+=(xx*-1); y+=0; context.lineTo(x,y);
x+=(xx*-1); y+=(yy*-1); context.lineTo(x,y);
x+=xx; y+=(yy*-1); context.lineTo(x,y);
context.fillStyle = "#FFFF99";
context.fill();
context.strokeStyle = "rgba(0,0,0,1)";
context.stroke();
}
function isEven(n)
{
return parseFloat(n) && (n % 2 == 0);
}
</script>
</section>
</body>
I have marked the HexagonObj creation that works and that dose not work.
You need to declare x and y as variables in each function where they are used. Because you are missing the var declaration, the functions are all accessing global x and y variables. As a consequence, the first call to HexagonObj clobbers the loop variables in main_init().
(Technically, you only need to declare var x, y in one of the functions to solve the immediate problem. However, it's bad form to be using global variables like that.)
for loop executing only once in function main_init since x and y which are global are modified inside HexagonObj function to y:81 and x:50