Related
I have seen a good animation in codepen.io Link Here . Its really amazing. But instead of red-arrow i thought to change it to Heart(Love symbol). So i started to debug the code and i found the function drawArrow, where they are drawing the arrow. There i have tried to implement heart draw(got the code from here).
var ctx = _pexcanvas.getContext("2d");
var d = 20; //The Size of the hearting
var k =150; // The Position of the heart
ctx.moveTo(k, k + d / 4);
ctx.quadraticCurveTo(k, k, k + d / 4, k);
ctx.quadraticCurveTo(k + d / 2, k, k + d / 2, k + d / 4);
ctx.quadraticCurveTo(k + d / 2, k, k + d * 3/4, k);
ctx.quadraticCurveTo(k + d, k, k + d, k + d / 4);
ctx.quadraticCurveTo(k + d, k + d / 2, k + d * 3/4, k + d * 3/4);
ctx.lineTo(k + d / 2, k + d);
ctx.lineTo(k + d / 4, k + d * 3/4);
ctx.quadraticCurveTo(k, k + d / 2, k, k + d / 4);
But while using the above code i can't change the starting position of the heart, means if i increase the K value its placing the heart in the linear position(Like heart will appear only in the linear scope of (-x,y) to (x,-y)),
But i like to place to heart in anywhere in the screen, but i can't achieve it. Give me right direction to fix it.
Note: I am not an JavaScript Dev, i have a basic knowledge in java.
Thanks in advance.
You just need to add the new function to draw hearts instead of arrow, the solution you are referred will draw a stable heart.
Width and height can be use as per your requirement.
Add the following function:
function drawHeart(fromx, fromy, tox, toy,lw,hlen,color) {
var x = fromx;
var y = fromy;
var width = lw ;
var height = hlen;
ctx.save();
ctx.beginPath();
var topCurveHeight = height * 0.3;
ctx.moveTo(x, y + topCurveHeight);
// top left curve
ctx.bezierCurveTo(
x, y,
x - width / 2, y,
x - width / 2, y + topCurveHeight
);
// bottom left curve
ctx.bezierCurveTo(
x - width / 2, y + (height + topCurveHeight) / 2,
x, y + (height + topCurveHeight) / 2,
x, y + height
);
// bottom right curve
ctx.bezierCurveTo(
x, y + (height + topCurveHeight) / 2,
x + width / 2, y + (height + topCurveHeight) / 2,
x + width / 2, y + topCurveHeight
);
// top right curve
ctx.bezierCurveTo(
x + width / 2, y,
x, y,
x, y + topCurveHeight
);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
ctx.restore();
}
Below is the working solution for same.
"use strict"
var stage = {
w:1280,
h:720
}
var _pexcanvas = document.getElementById("canvas");
_pexcanvas.width = stage.w;
_pexcanvas.height = stage.h;
var ctx = _pexcanvas.getContext("2d");
var pointer = {
x:0,
y:0
}
var scale = 1;
var portrait = true;
var loffset = 0;
var toffset = 0;
var mxpos = 0;
var mypos = 0;
// ------------------------------------------------------------------------------- Gamy
function drawArrow(fromx, fromy, tox, toy,lw,hlen,color) {
var dx = tox - fromx;
var dy = toy - fromy;
var angle = Math.atan2(dy, dx);
ctx.fillStyle=color;
ctx.strokeStyle=color;
ctx.lineCap = "round";
ctx.lineWidth = lw;
ctx.beginPath();
ctx.moveTo(fromx, fromy);
ctx.lineTo(tox, toy);
ctx.stroke();
ctx.beginPath();
ctx.moveTo(tox, toy);
ctx.lineTo(tox - hlen * Math.cos(angle - Math.PI / 6), toy - hlen * Math.sin(angle - Math.PI / 6));
ctx.lineTo(tox - hlen * Math.cos(angle)/2, toy - hlen * Math.sin(angle)/2);
ctx.lineTo(tox - hlen * Math.cos(angle + Math.PI / 6), toy - hlen * Math.sin(angle + Math.PI / 6));
ctx.closePath();
ctx.stroke();
ctx.fill();
}
function drawHeart(fromx, fromy, tox, toy,lw,hlen,color) {
var x = fromx;
var y = fromy;
var width = lw ;
var height = hlen;
ctx.save();
ctx.beginPath();
var topCurveHeight = height * 0.3;
ctx.moveTo(x, y + topCurveHeight);
// top left curve
ctx.bezierCurveTo(
x, y,
x - width / 2, y,
x - width / 2, y + topCurveHeight
);
// bottom left curve
ctx.bezierCurveTo(
x - width / 2, y + (height + topCurveHeight) / 2,
x, y + (height + topCurveHeight) / 2,
x, y + height
);
// bottom right curve
ctx.bezierCurveTo(
x, y + (height + topCurveHeight) / 2,
x + width / 2, y + (height + topCurveHeight) / 2,
x + width / 2, y + topCurveHeight
);
// top right curve
ctx.bezierCurveTo(
x + width / 2, y,
x, y,
x, y + topCurveHeight
);
ctx.closePath();
ctx.fillStyle = color;
ctx.fill();
ctx.restore();
}
var colors = ['#1abc9c','#1abc9c','#3498db','#9b59b6','#34495e','#16a085','#27ae60','#2980b9','#8e44ad','#2c3e50','#f1c40f','#e67e22','#e74c3c','#95a5a6','#f39c12','#d35400','#c0392b','#bdc3c7','#7f8c8d'];
ctx.clearRect(0,0,stage.w,stage.h);
for (var i =0;i<200;i++) {
var angle = Math.random()*Math.PI*2;
var length = Math.random()*250+50;
var myx=360+Math.sin(angle)*length;
var myy=360-Math.cos(angle)*length;
drawArrow(myx,myy,myx+length/6*Math.sin(angle),myy-length/6*Math.cos(angle),length/30,length/30,'#c0392b');
}
var explode = new Image();
explode.src = canvas.toDataURL("image/png");
ctx.clearRect(0,0,stage.w,stage.h);
for (var i =0;i<200;i++) {
var angle = Math.random()*Math.PI-Math.PI/2;
var length = Math.random()*480+50;
var myx=stage.w/2+Math.sin(angle)*length;
var myy=stage.h-Math.cos(angle)*length;
drawArrow(myx,myy,myx+length/6*Math.sin(angle),myy-length/6*Math.cos(angle),length/30,length/30,'#2c3e50');
}
var explodeb = new Image();
explodeb.src = canvas.toDataURL("image/png");
ctx.clearRect(0,0,stage.w,stage.h);
ctx.fillStyle = "rgba(236,240,241,1)";
ctx.fillRect(0,0,stage.w,stage.h);
for (var i =0;i<200;i++) {
var angle = Math.random()*Math.PI/Math.PI*180;
var length = Math.random()*250+50;
var myx=Math.random()*stage.w;
var myy=Math.random()*stage.h;
drawArrow(myx,myy,myx+length/6*Math.sin(angle),myy-length/6*Math.cos(angle),length/30,length/30,colors[Math.floor(Math.random()*colors.length)]);
}
ctx.fillStyle = "rgba(236,240,241,0.9)";
ctx.fillRect(0,0,stage.w,stage.h);
var back = new Image();
back.src = canvas.toDataURL("image/png");
var angle=0;
var ai = true;
var ait = 0;
var btm=0;
var bullets = [];
function Bullet() {
this.x=stage.w/2-Math.sin(angle)*150;
this.y=stage.h-Math.cos(angle)*150;
this.r=angle;
}
var enemies = [];
function Enemy() {
this.r = Math.random()*Math.PI/(2.5/2)-Math.PI/2.5;
this.dis = Math.random()*1280+720;
this.x=stage.w/2-Math.sin(this.r)*this.dis;
this.y=stage.h-Math.cos(this.r)*this.dis;
}
for(var i=0;i<10;i++) {
enemies.push(new Enemy());
enemies[i].x += Math.sin(enemies[i].r)*300;
enemies[i].y += Math.cos(enemies[i].r)*300;
}
var explosions = [];
function Explosion(x,y,ty) {
this.x=x;
this.y=y;
this.t=30;
this.ty=ty;
}
var eturn = 0;
var cold = [];
function enginestep() {
ctx.drawImage(back,0,0);
if (!ai&&ait<Date.now()-3000) {
ai = true;
}
btm++;
if(btm>8){
btm=0;
bullets.push(new Bullet());
}
for (var i=0;i<bullets.length;i++) {
bullets[i].x -= Math.sin(bullets[i].r)*20;
bullets[i].y -= Math.cos(bullets[i].r)*20;
drawArrow(bullets[i].x+Math.sin(bullets[i].r)*50,bullets[i].y+Math.cos(bullets[i].r)*50,bullets[i].x,bullets[i].y,8,8,'#2980b9');
if(bullets[i].x<-100||bullets[i].x>stage.w+100){
bullets.splice(i,1);
}
if(bullets[i].y<-100||bullets[i].y>stage.h+100){
bullets.splice(i,1);
}
}
for(var i=0;i<enemies.length;i++) {
enemies[i].x += Math.sin(enemies[i].r)*3;
enemies[i].y += Math.cos(enemies[i].r)*3;
drawHeart(enemies[i].x-Math.sin(enemies[i].r)*100,enemies[i].y-Math.cos(enemies[i].r)*100,enemies[i].x,enemies[i].y,15,15,"#c0392b");
if (enemies[i].y>stage.h) {
enemies[i] = new Enemy();
explosions.push(new Explosion(stage.w/2,stage.h,2));
shake = true;
shaket=0;
}
for (var b=0;b<bullets.length;b++) {
var dx = enemies[i].x-bullets[b].x;
var dy = enemies[i].y-bullets[b].y;
var dis = dx*dx+dy*dy;
if (dis<20*20) {
explosions.push(new Explosion(enemies[i].x,enemies[i].y,1));
enemies[i] = new Enemy();
bullets.splice(b,1);
}
}
}
if (ai) {
for(var l=0;l<enemies.length;l++) {
var dx = enemies[l].x-stage.w/2;
var dy = enemies[l].y-stage.h;
var dis = Math.floor(Math.sqrt(dx*dx+dy*dy));
var val1 = 100000+dis;
var val2 = 1000+l;
cold[l]=val1+'x'+val2;
}
cold.sort();
eturn = parseInt(cold[0].slice(8,11));
if (parseInt(cold[0].slice(1,6))<800) {
angle += (enemies[eturn].r-angle)/8;
}
} else {
var dx = pointer.x-stage.w/2;
var dy = pointer.y-stage.h;
angle = Math.atan(dx/dy);
}
drawArrow(stage.w/2,stage.h,stage.w/2-Math.sin(angle)*150,stage.h-Math.cos(angle)*150,30,20,'#2c3e50');
for(var e=0;e<explosions.length;e++) {
if (explosions[e].ty==1) {
var myimg = explode;
ctx.globalAlpha=1-(explosions[e].t/stage.h);
ctx.drawImage(myimg,explosions[e].x-explosions[e].t/2,explosions[e].y-explosions[e].t/2,explosions[e].t*stage.w/stage.h,explosions[e].t);
ctx.globalAlpha=1;
} else {
var myimg = explodeb;
ctx.globalAlpha=1-(explosions[e].t/stage.h);
ctx.drawImage(myimg,explosions[e].x-explosions[e].t*stage.w/stage.h/2,stage.h-explosions[e].t,explosions[e].t*stage.w/stage.h,explosions[e].t);
ctx.globalAlpha=1;
}
}
for(var e=0;e<explosions.length;e++) {
explosions[e].t += 20;
if (explosions[e].t>stage.h) {
explosions.splice(e,1);
}
}
}
// ------------------------------------------------------------------------------- events
// ------------------------------------------------------------------------------- events
// ------------------------------------------------------------------------------- events
// ------------------------------------------------------------------------------- events
function toggleFullScreen() {
var doc = window.document;
var docEl = doc.documentElement;
var requestFullScreen = docEl.requestFullscreen || docEl.mozRequestFullScreen || docEl.webkitRequestFullScreen || docEl.msRequestFullscreen;
var cancelFullScreen = doc.exitFullscreen || doc.mozCancelFullScreen || doc.webkitExitFullscreen || doc.msExitFullscreen;
if(!doc.fullscreenElement && !doc.mozFullScreenElement && !doc.webkitFullscreenElement && !doc.msFullscreenElement) {
requestFullScreen.call(docEl);
}
else {
cancelFullScreen.call(doc);
}
}
function motchstart(e) {
mxpos = (e.pageX-loffset)*scale;
mypos = (e.pageY-toffset)*scale;
}
function motchmove(e) {
mxpos = (e.pageX-loffset)*scale;
mypos = (e.pageY-toffset)*scale;
pointer.x = mxpos;
pointer.y = mypos;
ai = false;
ait = Date.now();
}
function motchend(e) {
}
window.addEventListener('mousedown', function(e) {
motchstart(e);
}, false);
window.addEventListener('mousemove', function(e) {
motchmove(e);
}, false);
window.addEventListener('mouseup', function(e) {
motchend(e);
}, false);
window.addEventListener('touchstart', function(e) {
e.preventDefault();
motchstart(e.touches[0]);
}, false);
window.addEventListener('touchmove', function(e) {
e.preventDefault();
motchmove(e.touches[0]);
}, false);
window.addEventListener('touchend', function(e) {
e.preventDefault();
motchend(e.touches[0]);
}, false);
// ------------------------------------------------------------------------ stager
// ------------------------------------------------------------------------ stager
// ------------------------------------------------------------------------ stager
// ------------------------------------------------------------------------ stager
function _pexresize() {
var cw = window.innerWidth;
var ch = window.innerHeight;
if (cw<=ch*stage.w/stage.h) {
portrait = true;
scale = stage.w/cw;
loffset = 0;
toffset = Math.floor(ch-(cw*stage.h/stage.w))/2;
_pexcanvas.style.width = cw + "px";
_pexcanvas.style.height = Math.floor(cw*stage.h/stage.w) + "px";
_pexcanvas.style.marginLeft = loffset +"px";
_pexcanvas.style.marginTop = toffset +"px";
} else {
scale = stage.h/ch;
portrait = false;
loffset = Math.floor(cw-(ch*stage.w/stage.h))/2;
toffset = 0;
_pexcanvas.style.height = ch + "px";
_pexcanvas.style.width = Math.floor(ch*stage.w/stage.h) + "px";
_pexcanvas.style.marginLeft = loffset +"px";
_pexcanvas.style.marginTop = toffset +"px";
}
}
window.requestAnimFrame = (function(){
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function( callback ){
window.setTimeout(callback, 1000 / 60);
};})();
function sfps(iny) {
return(Math.floor(smoothfps)/60*iny);
}
var timebomb=0;
var lastCalledTime;
var fpcount = 0;
var fpall = 0;
var smoothfps = 60;
var thisfps = 60;
function fpscounter() {
timebomb ++;
if (!lastCalledTime) {
lastCalledTime = Date.now();
return;
}
var delta = (Date.now()-lastCalledTime)/1000;
lastCalledTime = Date.now();
var fps = 1/delta;
fpcount ++;
fpall += fps;
if (timebomb>30) {
thisfps = parseInt(fpall/fpcount*10)/10;
fpcount = 0;
fpall = 0;
timebomb = 0;
}
}
var shake = false;
var shaket = 0;
function animated() {
requestAnimFrame(animated);
if (shake) {
var trax = Math.random()*60-30;
var tray = Math.random()*60-30;
ctx.translate(trax,tray);
}
// fpscounter();
//ctx.clearRect(0,0,_pexcanvas.width,_pexcanvas.height);
enginestep()
// ctx.fillStyle='#8e44ad';
// ctx.font = "24px arial";
// ctx.textAlign = "left";
// ctx.fillText(thisfps,20,50);
// smoothfps += (thisfps-smoothfps)/100;
// ctx.fillText(cold[0].slice(1,6),20,80);
// ctx.beginPath();
// ctx.arc(pointer.x, pointer.y, 50, 0, Math.PI*2,false);
// ctx.closePath();
// ctx.fill();
if (shake) {
ctx.translate(-trax,-tray);
shaket ++;
if (shaket>20) {
shaket=0;
shake=false;
}
}
}
_pexresize();
animated();
body {background:#000000;margin:0;padding:0}
canvas {background:#ecf0f1;}
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="description" content="Pex.Js Engine">
<meta name="author" content="Ahmad Faisal Jawed">
<title>Arrows</title>
</head>
<body onresize='_pexresize()'>
<canvas id='canvas' width=1280 height=720></canvas>
</body>
</html>
Button Left - pushes blue lines with X's
Button Right - pushes red lines with O's
Button Delete - removes last line in array
So, when I click on Left - blue lines with X's are pushed into an array.
Problem is... if I click on Right and start adding red lines, the blue lines that are already drawn on canvas also change in red lines with O's.
How can I separate the 2 buttons from interfering with each other ?
Thank you.
function drawGrid() {
ctx.strokeStyle = "black";
ctx.lineWidth = 0.1;
ctx.beginPath();
for (x = 15; x <= w; x += 60) {
ctx.moveTo(x, 0);
ctx.lineTo(x, h);
for (y = 20; y <= h; y += 20) {
ctx.moveTo(0, y);
ctx.lineTo(w, y);
}
}
ctx.stroke();
}
var ry = [[]];
var canvas = document.querySelector("#myCanvas");
var w = (canvas.width = 450);
var h = (canvas.height = 280);
var ctx = canvas.getContext("2d");
drawGrid();
// Right Button <--------------------------------------------------
document.getElementById('right').onclick = function () {
ry.push([]);
myCanvas.addEventListener("click", e => {
var offset = canvas.getBoundingClientRect();
var x = e.clientX - offset.left;
var y = e.clientY - offset.top;
ry[ry.length - 1].push({ x: x, y: y });
ctx.clearRect(0, 0, w, h);
drawGrid();
drawChart();
});
deletes.addEventListener("click", e => {
if (ry[ry.length - 1].length > 0) {
ry[ry.length - 1].pop();
} else {
ry.pop();
ry[ry.length - 1].pop();
}
ctx.clearRect(0, 0, w, h);
drawGrid();
drawChart();
});
function drawGrid() {
ctx.strokeStyle = "black";
ctx.lineWidth = 0.1;
ctx.beginPath();
for (x = 15; x <= w; x += 60) {
ctx.moveTo(x, 0);
ctx.lineTo(x, h);
for (y = 20; y <= h; y += 20) {
ctx.moveTo(0, y);
ctx.lineTo(w, y);
}
}
ctx.stroke();
}
function drawChart() {
ctx.lineWidth = 1;
for (let index = 0; index < ry.length; index++) {
for (let i = 0; i < ry[index].length; i++) {
let l = ry[index][i];
drawCircle(l.x, l.y);
if (i > 0) {
let last = ry[index][i - 1];
ctx.beginPath();
ctx.moveTo(last.x, last.y);
ctx.lineTo(l.x, l.y);
ctx.strokeStyle = "red";
ctx.stroke();
}
}
}
}
function drawCircle(x, y) {
ctx.beginPath();
ctx.arc(x, y, 6, 0, Math.PI * 2);
ctx.strokeStyle = "red";
ctx.stroke();
}
};
// Left Button <--------------------------------------------------
document.getElementById('left').onclick = function () {
ry.push([]);
myCanvas.addEventListener("click", e => {
var offset = canvas.getBoundingClientRect();
var x = e.clientX - offset.left;
var y = e.clientY - offset.top;
ry[ry.length - 1].push({ x: x, y: y });
ctx.clearRect(0, 0, w, h);
drawGrid();
drawChart();
});
function drawGrid() {
ctx.strokeStyle = "black";
ctx.lineWidth = 0.1;
ctx.beginPath();
for (x = 15; x <= w; x += 60) {
ctx.moveTo(x, 0);
ctx.lineTo(x, h);
for (y = 20; y <= h; y += 20) {
ctx.moveTo(0, y);
ctx.lineTo(w, y);
}
}
ctx.stroke();
}
function drawChart() {
ctx.lineWidth = 1;
for (let index = 0; index < ry.length; index++) {
for (let i = 0; i < ry[index].length; i++) {
let l = ry[index][i];
drawCross(l.x, l.y);
if (i > 0) {
let last = ry[index][i - 1];
ctx.beginPath();
ctx.moveTo(last.x, last.y);
ctx.lineTo(l.x, l.y);
ctx.strokeStyle = "blue";
ctx.stroke();
}
}
}
}
function drawCross(x, y) {
ctx.beginPath();
ctx.moveTo(x - 6, y - 6);
ctx.lineTo(x + 6, y + 6);
ctx.moveTo(x + 6, y - 6);
ctx.lineTo(x - 6, y + 6);
ctx.strokeStyle = "blue";
ctx.stroke();
}
};
A nice thing to know is that you can call a function as a window method. For example if you have a function test() you can call this function as window["test"]()
In your case inside the function drawChart() you can call either drawCircle() or drawCross()
When I push the points into the points array, besides the x and the y I add a f (for function). The value of f is a string with the name of the function: drawCircle or drawCross
Inside the function drawChart you'll find this line of code: window[l.f](l.x, l.y) This is calling either drawCircle() or drawCross() depending on the value of l.f: the name of the function.
The name of the function is a global variable declared at the beginning of my code and is set to drawCircle: let theFunction = "drawCircle";
You change the value of theFunction when you click the right or left buttons.
//this is an array of arrays
//when I click on the canvas a new point is pushed on the last array of this array
var ry = [[]];
var canvas = document.querySelector("#myCanvas");
let w = (canvas.width = 450);
let h = (canvas.height = 280);
var ctx = canvas.getContext("2d");
let theFunction = "drawCircle";
drawGrid();
function drawCircle(x, y) {
ctx.beginPath();
ctx.arc(x, y, 6, 0, Math.PI * 2);
ctx.strokeStyle = "red";
ctx.stroke();
}
function drawCross(x, y) {
ctx.beginPath();
ctx.moveTo(x - 6, y - 6);
ctx.lineTo(x + 6, y + 6);
ctx.moveTo(x + 6, y - 6);
ctx.lineTo(x - 6, y + 6);
ctx.strokeStyle = "blue";
ctx.stroke();
}
myCanvas.addEventListener("click", e => {
var offset = canvas.getBoundingClientRect();
var x = e.clientX - offset.left;
var y = e.clientY - offset.top;
//a new point is pushed on the last array of ry
ry[ry.length - 1].push({ x: x, y: y, f:theFunction });
// delete everything
ctx.clearRect(0, 0, w, h);
// draw everything
drawGrid();
drawChart();
});
sterge.addEventListener("click", e => {
//when sterge is clicked the last point from the last array is deleted
if (ry[ry.length - 1].length > 0) {
ry[ry.length - 1].pop();
} else {
//if the last array is empty I delete this array
ry.pop();
//and then I delete the last point from the last array
ry[ry.length - 1].pop();
}
// delete everything
ctx.clearRect(0, 0, w, h);
// draw everything
drawGrid();
drawChart();
});
dreapta.addEventListener("click", e => {
theFunction = "drawCircle"
//when dreapta is clicked, a new array is pushed into the ry
ry.push([]);
});
stanga.addEventListener("click", e => {
theFunction = "drawCross"
//when dreapta is clicked, a new array is pushed into the ry
ry.push([]);
});
function drawGrid() {
ctx.strokeStyle = "black";
ctx.lineWidth = 0.1;
ctx.beginPath();
for (x = 15; x <= w; x += 60) {
ctx.moveTo(x, 0);
ctx.lineTo(x, h);
for (y = 20; y <= h; y += 20) {
ctx.moveTo(0, y);
ctx.lineTo(w, y);
}
}
ctx.stroke();
}
function drawChart() {
ctx.lineWidth = 1;
// for every array in the ry array
for (let index = 0; index < ry.length; index++) {
// for every point in the ry[index]
for (let i = 0; i < ry[index].length; i++) {
let l = ry[index][i];
// draw the circle or the cross
window[l.f](l.x, l.y)
// draw the line
if (i > 0) {
let last = ry[index][i - 1];
ctx.beginPath();
ctx.moveTo(last.x, last.y);
ctx.lineTo(l.x, l.y);
ctx.strokeStyle = "blue";
ctx.stroke();
}
}
}
}
<canvas id="myCanvas"></canvas>
<p><input type="button" value="dreapta" id="dreapta" />
<input type="button" value="stanga" id="stanga" />
<input type="button" value="sterge" id="sterge" />
</p>
I create this animaiton using canvas and converting svg's to canvas shapes. Most times it runs it heats up my computer and the fan starts going.
Just wondering if there is something about the code, html5 canvas, canvas paths or the animation recursion that is so intensive?
View on codepen: https://codepen.io/benbyford-the-lessful/pen/ZjjVdR?editors=1010#
// check program is being run
console.log('bg animation running...');
// setup canvas
var canvas = document.getElementById('bgCanvas');
var ctx = canvas.getContext('2d')
// redo this - canvas size
//
var width = window.innerWidth,
height = window.innerHeight;
canvas.width = width * 2;
canvas.height = height * 2;
var gridSquareWidth = 20;
var gridWidth = (width * 2) / gridSquareWidth,
gridHeight = (height * 2) / gridSquareWidth;
var grid = [];
// create default grid array
for (var x = 0; x < gridWidth; x++) {
grid[x] = [];
for (var y = 0; y < gridHeight; y++) {
var rand = getRandomArbitrary(0,5);
var rand2 = getRandomArbitrary(0,2);
if(rand2 == 1 || x < (gridWidth / 4) || x > (gridWidth / 2) || y < (gridHeight / 4) || y > (gridHeight / 2)){
rand--;
}
if(rand > 2) grid[x][y] = 1;
}
}
//
// main update function
//
var animationSpeed = 0.1;
var animationSpeedCount = 0;
var running = true;
function update(dt) {
if(running){
animationSpeedCount += dt;
if(animationSpeedCount > animationSpeed){
moveGrid();
animationSpeedCount = 0;
}
draw();
}
}
var noOfFrames = 3;
var waveOffset = 15;
var increment = 0;
function moveGrid() {
var x = increment;
var x2 = increment - noOfFrames - waveOffset;
// add frmae wave
for (var i = 0; i < noOfFrames; i++) {
moveONeFrameForward(x, true);
x--;
}
// go back frmae wave
for (var i = 0; i < noOfFrames; i++) {
moveONeFrameForward(x2, false);
x2--;
}
// var x column, add of subtract by 1
function moveONeFrameForward(x, add){
if(x < 0){
x = Math.ceil(gridWidth + x);
}
if(x > 0 && x < gridWidth){
for (var y = 0; y < gridHeight; y++) {
if(grid[x][y] > 0){
if(add){
grid[x][y] = grid[x][y] + 1;
}else{
if(grid[x][y] > 1) grid[x][y] = grid[x][y] - 1;
}
}
}
}
}
// increment column
increment += 1;
if(increment > gridWidth){
increment = 0;
// stop running
// running = false;
}
}
var fills = ["#eeeeee","#efefef","#fefefe","#ffffff"];
function draw() {
// clear canvas to white
ctx.fillStyle = '#dddddd';
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (var x = 0; x < gridWidth; x++) {
for (var y = 0; y < gridHeight; y++) {
var offsetX = x * gridSquareWidth;
var offsetY = y * gridSquareWidth;
var frame = 0;
switch (grid[x][y]) {
case 1:
frame = 1
break;
case 2:
frame = 2;
break;
case 3:
frame = 3;
break;
case 4:
frame = 4;
break;
default:
}
if(frame) drawframe(ctx, frame, offsetX, offsetY, fills);
}
}
}
// The main game loop
var lastTime = 0;
function gameLoop() {
var now = Date.now();
var dt = (now - lastTime) / 1000.0;
update(dt);
lastTime = now;
window.requestAnimationFrame(gameLoop);
};
// start game
gameLoop();
//
// UTILITIES
//
// cross browser requestAnimationFrame - https://gist.github.com/mrdoob/838785
if ( !window.requestAnimationFrame ) {
window.requestAnimationFrame = ( function() {
return window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
window.oRequestAnimationFrame ||
window.msRequestAnimationFrame ||
function(
/* function FrameRequestCallback */ callback, /* DOMElement Element */ element ) {
window.setTimeout( callback, 1000 / 60 );
};
})();
}
function getRandomArbitrary(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
var frame1Center = 4.1;
var frame2Center = 2.1;
function drawframe(ctx, frame, x, y, fills) {
ctx.strokeStyle = 'rgba(0,0,0,0)';
ctx.lineCap = 'butt';
ctx.lineJoin = 'miter';
ctx.miterLimit = 4;
ctx.fillStyle = fills[frame-1];
switch (frame) {
case 1:
ctx.beginPath();
ctx.moveTo(3.1+x+frame1Center,0+y);
ctx.lineTo(0.6+x+frame1Center,0+y);
ctx.bezierCurveTo(0.3+x+frame1Center,0+y,0+x+frame1Center,0.3+y,0+x+frame1Center,0.6+y);
ctx.lineTo(0.3+x+frame1Center,12.1+y);
ctx.bezierCurveTo(0.3+x+frame1Center,12.4+y,0.6+x+frame1Center,12.7+y,0.8999999999999999+x+frame1Center,12.7+y);
ctx.lineTo(3.4+x+frame1Center,12.7+y);
ctx.bezierCurveTo(3.6999999999999997+x+frame1Center,12.7+y,4+x+frame1Center,12.399999999999999+y,4+x+frame1Center,12.1+y);
ctx.lineTo(4+x+frame1Center,0.6+y);
ctx.bezierCurveTo(4.1+x+frame1Center,0.3+y,3.7+x+frame1Center,0+y,3.1+x+frame1Center,0+y);
ctx.closePath();
ctx.fill();
ctx.stroke();
break;
case 2 || 6:
ctx.beginPath();
ctx.moveTo(4.4+x+frame2Center,0+y);
ctx.bezierCurveTo(3.1+x+frame2Center,0+y,0+x+frame2Center,0.8+y,0+x+frame2Center,2.1+y);
ctx.bezierCurveTo(0+x+frame2Center,3.4000000000000004+y,0.3+x+frame2Center,12.5+y,1.6+x+frame2Center,12.799999999999999+y);
ctx.bezierCurveTo(2.8+x+frame2Center,13+y,6+x+frame2Center,12+y,6+x+frame2Center,10.7+y);
ctx.bezierCurveTo(6+x+frame2Center,9.1+y,5.7+x+frame2Center,0+y,4.4+x+frame2Center,0+y);
ctx.closePath();
ctx.fill();
ctx.stroke();
break;
case 3 || 5:
ctx.beginPath();
ctx.moveTo(5.2+x,0 +y);
ctx.bezierCurveTo(7.5 +x,0+y,9.3+x,6.5+y,9.3 +x,8.7+y);
ctx.bezierCurveTo(9.3+x,10.899999999999999+y,6.300000000000001+x,12.799999999999999+y,4.1000000000000005+x,12.799999999999999+y);
ctx.bezierCurveTo(1.9000000000000004+x,12.799999999999999+y,0+x,6.3+y,0+x,4.1+y);
ctx.bezierCurveTo(0+x,1.8999999999999995+y,3+x,0+y,5.2+x,0+y);
ctx.closePath();
ctx.fill();
ctx.stroke();
break;
case 4:
ctx.beginPath();
ctx.arc(5.9+x,6.3+y,5.8,0,6.283185307179586,true);
ctx.closePath();
ctx.fill();
ctx.stroke();
break;
default:
}
};
It's a bit hard to tell exactly "why", but there are definitely some things that could be improved.
First, you are drawing twice as big as what is needed.
Set you canvas size to the rendered one, and you'll probably see a big improvement in performances.
Then, you are drawing a lot of sub-path at every draw (and setting a lot of times the context's properties for nothing).
You could try to merge all these sub-paths in bigger ones, grouped by fillStyle, so that the rasterizer works only four times per frame. This can also improve performances a bit.
But the approach I would personally take, is to pre-render all the 4 different states on 4 different canvases. Then, use only drawImage to draw the required strip.
In best case, you end up with only 4 calls to drawImage, in worth one, with 8 calls.
Here is a rough proof of concept:
// setup canvas
var canvas = document.getElementById('bgCanvas');
var ctx = canvas.getContext('2d')
// don't set it twice as big as needed
var width = window.innerWidth,
height = window.innerHeight;
canvas.width = width;
canvas.height = height;
var gridSquareWidth = 10;
var gridWidth = (width) / gridSquareWidth,
gridHeight = (height) / gridSquareWidth;
var grid = [];
// create default grid array
for (var x = 0; x < gridWidth; x++) {
grid[x] = [];
for (var y = 0; y < gridHeight; y++) {
var rand = getRandomArbitrary(0, 5);
var rand2 = getRandomArbitrary(0, 2);
if (rand2 == 1 || x < (gridWidth / 4) || x > (gridWidth / 2) || y < (gridHeight / 4) || y > (gridHeight / 2)) {
rand--;
}
if (rand > 2) grid[x][y] = 1;
}
}
var fills = ["#eeeeee", "#efefef", "#fefefe", "#ffffff"];
var frame1Center = 4.1;
var frame2Center = 2.1;
// the 4 points drawers
var drawers = [draw0, draw1, draw2, draw3];
// initialise our four possible states
var states = [
initState(0),
initState(1),
initState(2),
initState(3)
];
//
// main update function
//
var running = true;
var speed = 2;
var waveWidth = 200;
var waveMargin = gridSquareWidth * 4;
var waveStart = 0;
var waveEnd = waveWidth;
// start game
update();
function initState(status) {
var c = canvas.cloneNode();
var ctx = c.getContext('2d');
ctx.scale(0.5, 0.5); // to circumvent values being set for scale(2)
ctx.beginPath(); // single path
ctx.fillStyle = fills[status];
for (var x = 0; x < gridWidth; x++) {
for (var y = 0; y < gridHeight; y++) {
if (grid[x][y]) {
drawers[status](ctx, x * gridSquareWidth * 2, y * gridSquareWidth * 2);
}
}
}
ctx.fill(); // single fill
return c;
}
function draw0(ctx, x, y) {
ctx.moveTo(3.1 + x + frame1Center, 0 + y);
ctx.lineTo(0.6 + x + frame1Center, 0 + y);
ctx.bezierCurveTo(0.3 + x + frame1Center, 0 + y, 0 + x + frame1Center, 0.3 + y, 0 + x + frame1Center, 0.6 + y);
ctx.lineTo(0.3 + x + frame1Center, 12.1 + y);
ctx.bezierCurveTo(0.3 + x + frame1Center, 12.4 + y, 0.6 + x + frame1Center, 12.7 + y, 0.8999999999999999 + x + frame1Center, 12.7 + y);
ctx.lineTo(3.4 + x + frame1Center, 12.7 + y);
ctx.bezierCurveTo(3.6999999999999997 + x + frame1Center, 12.7 + y, 4 + x + frame1Center, 12.399999999999999 + y, 4 + x + frame1Center, 12.1 + y);
ctx.lineTo(4 + x + frame1Center, 0.6 + y);
ctx.bezierCurveTo(4.1 + x + frame1Center, 0.3 + y, 3.7 + x + frame1Center, 0 + y, 3.1 + x + frame1Center, 0 + y);
ctx.closePath();
}
function draw1(ctx, x, y) {
ctx.moveTo(4.4 + x + frame2Center, 0 + y);
ctx.bezierCurveTo(3.1 + x + frame2Center, 0 + y, 0 + x + frame2Center, 0.8 + y, 0 + x + frame2Center, 2.1 + y);
ctx.bezierCurveTo(0 + x + frame2Center, 3.4000000000000004 + y, 0.3 + x + frame2Center, 12.5 + y, 1.6 + x + frame2Center, 12.799999999999999 + y);
ctx.bezierCurveTo(2.8 + x + frame2Center, 13 + y, 6 + x + frame2Center, 12 + y, 6 + x + frame2Center, 10.7 + y);
ctx.bezierCurveTo(6 + x + frame2Center, 9.1 + y, 5.7 + x + frame2Center, 0 + y, 4.4 + x + frame2Center, 0 + y);
ctx.closePath();
}
function draw2(ctx, x, y) {
ctx.moveTo(5.2 + x, 0 + y);
ctx.bezierCurveTo(7.5 + x, 0 + y, 9.3 + x, 6.5 + y, 9.3 + x, 8.7 + y);
ctx.bezierCurveTo(9.3 + x, 10.899999999999999 + y, 6.300000000000001 + x, 12.799999999999999 + y, 4.1000000000000005 + x, 12.799999999999999 + y);
ctx.bezierCurveTo(1.9000000000000004 + x, 12.799999999999999 + y, 0 + x, 6.3 + y, 0 + x, 4.1 + y);
ctx.bezierCurveTo(0 + x, 1.8999999999999995 + y, 3 + x, 0 + y, 5.2 + x, 0 + y);
ctx.closePath();
}
function draw3(ctx, x, y) {
ctx.moveTo(5.9 + x, 6.3 + y);
ctx.arc(5.9 + x, 6.3 + y, 5.8, 0, 2 * Math.PI);
}
function update(dt) {
if (running) {
draw();
moveGrid();
}
window.requestAnimationFrame(update);
}
function moveGrid() {
waveStart = (waveStart + speed) % canvas.width;
waveEnd = (waveStart + waveWidth) % canvas.width;
}
function draw() {
ctx.fillStyle = '#dddddd';
ctx.fillRect(0, 0, canvas.width, canvas.height);
var x = 0;
// the roll logic is a bit dirty... sorry.
if (waveEnd < waveStart) {
x = waveEnd - waveWidth;
drawStrip(1, x, waveMargin);
x = waveEnd - waveWidth + waveMargin;
drawStrip(3, x, (waveWidth - (waveMargin * 2)));
x = waveEnd - waveMargin;
drawStrip(2, x, waveMargin);
x = waveEnd;
}
drawStrip(0, x, waveStart - x);
drawStrip(1, waveStart, waveMargin);
drawStrip(3, waveStart + waveMargin, waveWidth - (waveMargin * 2));
drawStrip(2, waveStart + (waveWidth - waveMargin), waveMargin);
drawStrip(0, waveEnd, canvas.width - Math.max(waveEnd, waveStart));
}
function drawStrip(state, x, w) {
if(x < 0) w = w + x;
if (w <= 0) return;
x = Math.max(x, 0);
ctx.drawImage(states[state],
Math.max(x, 0), 0, w, canvas.height,
Math.max(x, 0), 0, w, canvas.height
);
}
function getRandomArbitrary(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
:root,body,canvas {margin: 0}
<canvas id="bgCanvas"></canvas>
I'm working on a certain layout where I need to draw a hexagon which needs to be clickable. I'm using the Path2D construct and isPointInPath function. I'm constructing an animation where a number of hexagons is created and then each moved to a certain position. After the movement is done, I am attaching onclick event handlers to certain hexagons. However there is weird behaviour.
Some initialized variables
const COLOR_DARK = "#73b6c6";
const COLOR_LIGHT = "#c3dadd";
const COLOR_PRIMARY = "#39a4c9";
const TYPE_PRIMARY = 'primary';
let hexagons = [];
Below is the function which draws the hexagons.
function drawHex(ctx, x, y, hexProps, stroke, color) {
let myPath = new Path2D();
myPath.moveTo(x + hexProps.width*0.5, y);
myPath.lineTo(x, y + hexProps.height*hexProps.facShort);
myPath.lineTo(x, y + hexProps.height*hexProps.facLong);
myPath.lineTo(x + hexProps.width*0.5, y + hexProps.height);
myPath.lineTo(x + hexProps.width, y + hexProps.height*hexProps.facLong);
myPath.lineTo(x + hexProps.width, y + hexProps.height*hexProps.facShort);
myPath.lineTo(x + hexProps.width*0.5, y);
myPath.closePath();
if (stroke){
ctx.strokeStyle = color;
ctx.stroke(myPath);
} else {
ctx.fillStyle = color;
ctx.fill(myPath);
}
return myPath;
}
This function populates the hexagon array
function populateLeftHex(canvasWidth, canvasHeight, hexProps) {
const startX = canvasWidth / 2;
const startY = canvasHeight / 2;
const baseLeft = canvasWidth * 0.05;
for(let i = 0; i < 5; i++){
let hexNumber = (i % 4 == 0)? 2: 1;
for(let j = 0; j < hexNumber; j++){
hexagons.push({
startX: startX,
startY: startY,
endX: baseLeft + (2 * j) + ((i % 2 == 0)? (hexProps.width * j) : (hexProps.width/2)),
endY: ((i + 1) * hexProps.height) - ((i) * hexProps.height * hexProps.facShort) + (i* 2),
stroke: true,
color: ( i % 2 == 0 && j % 2 == 0)? COLOR_DARK : COLOR_LIGHT,
type: TYPE_PRIMARY
});
}
}
}
And here is where Im calling the isPointInPath function.
window.onload = function (){
const c = document.getElementById('canvas');
const canvasWidth = c.width = window.innerWidth,
canvasHeight = c.height = window.innerHeight,
ctx = c.getContext('2d');
window.requestAnimFrame = (function (callback) {
return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function (callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
console.log(canvasWidth);
let hexProps = {
width: canvasWidth * 0.075,
get height () {
return this.width/Math.sqrt(3) + (1.5)*(this.width/Math.sqrt(2)/2);
} ,
facShort: 0.225,
get facLong () {
return 1 - this.facShort;
}
};
populateLeftHex(canvasWidth, canvasHeight, hexProps);
let pct = 0;
const fps = 200;
animate();
function animate () {
setTimeout(function () {
// increment pct towards 100%
pct += .03;
// if we're not done, request another animation frame
if (pct < 1.00) {
requestAnimFrame(animate);
} else { //if pct is no longer less than 1.00, then the movement animation is over.
hexagons.forEach(function (hex) {
if(hex.type === TYPE_PRIMARY) {
console.info(hex.path);
c.onclick = function(e) {
let x = e.clientX - c.offsetLeft,
y = e.clientY - c.offsetTop;
console.info(ctx.isPointInPath(hex.path, (e.clientX - c.offsetLeft), (e.clientY - c.offsetTop) ));
};
}
})
}
ctx.clearRect(0, 0, c.width, c.height);
// draw all hexagons
for ( let i = 0; i < hexagons.length; i++) {
// get reference to next shape
let hex = hexagons[i];
// note: dx/dy are fixed values
// they could be put in the shape object for efficiency
let dx = hex.endX - hex.startX;
let dy = hex.endY - hex.startY;
let nextX = hex.startX + dx * pct;
let nextY = hex.startY + dy * pct;
hex = hexagons[i];
ctx.fillStyle = hex.color;
hex.path = drawHex(ctx, nextX, nextY, hexProps, hex.stroke, hex.color);
}
}, 1000 / fps);
}
Can you help me figure out what I'm doing wrong? Maybe I misunderstood how Path2D works? Thanks in advance.
Had to do a bit of work to build a test page as your example is incomplete, but this is working for me - though my hexagon is concave...
var myCanvas = document.getElementById("myCanvas");
var ctx = myCanvas.getContext("2d");
var hexProps = {width:100, height:100, facShort:-2, facLong:10};
var hexagons = [];
function drawHex(ctx, x, y, hexProps, stroke, color) {
let myPath = new Path2D();
myPath.moveTo(x + hexProps.width*0.5, y);
myPath.lineTo(x, y + hexProps.height*hexProps.facShort);
myPath.lineTo(x, y + hexProps.height*hexProps.facLong);
myPath.lineTo(x + hexProps.width*0.5, y + hexProps.height);
myPath.lineTo(x + hexProps.width, y + hexProps.height*hexProps.facLong);
myPath.lineTo(x + hexProps.width, y + hexProps.height*hexProps.facShort);
myPath.lineTo(x + hexProps.width*0.5, y);
myPath.closePath();
if (stroke){
ctx.strokeStyle = color;
ctx.stroke(myPath);
} else {
ctx.fillStyle = color;
ctx.fill(myPath);
}
return myPath;
}
hexagons.push({type:0, path:drawHex(ctx,100,100,hexProps,false,"#0f0")});
hexagons.forEach(function (hex) {
if(hex.type === 0) {
console.info(hex.path);
myCanvas.onclick = function(e) {
let x = e.clientX - myCanvas.offsetLeft,
y = e.clientY - myCanvas.offsetTop;
console.info(x,y);
console.info(ctx.isPointInPath(hex.path, (e.clientX -
myCanvas.offsetLeft), (e.clientY - myCanvas.offsetTop) ));
};
}
})
<canvas width=500 height=500 id=myCanvas style='border:1px solid red'></canvas>
Test clicks give true and false where expected:
test.htm:48 165 168
test.htm:49 true
test.htm:48 151 336
test.htm:49 false
test.htm:48 124 314
test.htm:49 true
test.htm:48 87 311
test.htm:49 false
Hi I try to create an animation with a circle. The function drawRandom(drawFunctions) should pic one of the three drawcircle functions and should bring it on the canvas. Now the problem is that this function become executed every second (main loop) and the circle change his colour. How can I fix that?
window.onload = window.onresize = function() {
var C = 1; // canvas width to viewport width ratio
var el = document.getElementById("myCanvas");
var viewportWidth = window.innerWidth;
var viewportHeight = window.innerHeight;
var canvasWidth = viewportWidth * C;
var canvasHeight = viewportHeight;
el.style.position = "fixed";
el.setAttribute("width", canvasWidth);
el.setAttribute("height", canvasHeight);
var x = canvasWidth / 100;
var y = canvasHeight / 100;
var ballx = canvasWidth / 100;
var n;
window.ctx = el.getContext("2d");
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
// draw triangles
function init() {
ballx;
return setInterval(main_loop, 1000);
}
function drawcircle1()
{
var radius = x * 5;
ctx.beginPath();
ctx.arc(ballx * 108, canvasHeight / 2, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'yellow';
ctx.fill();
}
function drawcircle2()
{
var radius = x * 5;
ctx.beginPath();
ctx.arc(ballx * 108, canvasHeight / 2, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'blue';
ctx.fill();
}
function drawcircle3()
{
var radius = x * 5;
ctx.beginPath();
ctx.arc(ballx * 105, canvasHeight / 2, radius, 0, 2 * Math.PI, false);
ctx.fillStyle = 'orange';
ctx.fill();
}
function draw() {
var counterClockwise = false;
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
//first halfarc
ctx.beginPath();
ctx.arc(x * 80, y * 80, y * 10, 0 * Math.PI, 1 * Math.PI, counterClockwise);
ctx.lineWidth = y * 1;
ctx.strokeStyle = 'black';
ctx.stroke();
// draw stop button
ctx.beginPath();
ctx.moveTo(x * 87, y * 2);
ctx.lineTo(x * 87, y * 10);
ctx.lineWidth = x;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x * 95, y * 2);
ctx.lineTo(x * 95, y * 10);
ctx.lineWidth = x;
ctx.stroke();
function drawRandom(drawFunctions){
//generate a random index
var randomIndex = Math.floor(Math.random() * drawFunctions.length);
//call the function
drawFunctions[randomIndex]();
}
drawRandom([drawcircle1, drawcircle2, drawcircle3]);
}
function update() {
ballx -= 0.1;
if (ballx < 0) {
ballx = -radius;
}
}
function main_loop() {
draw();
update();
collisiondetection();
}
init();
function initi() {
console.log('init');
// Get a reference to our touch-sensitive element
var touchzone = document.getElementById("myCanvas");
// Add an event handler for the touchstart event
touchzone.addEventListener("mousedown", touchHandler, false);
}
function touchHandler(event) {
// Get a reference to our coordinates div
var can = document.getElementById("myCanvas");
// Write the coordinates of the touch to the div
if (event.pageX < x * 50 && event.pageY > y * 10) {
ballx += 1;
} else if (event.pageX > x * 50 && event.pageY > y * 10 ) {
ballx -= 1;
}
console.log(event, x, ballx);
draw();
}
initi();
draw();
}
Take a look at my code that I wrote:
var lastTime = 0;
function requestMyAnimationFrame(callback, time)
{
var t = time || 16;
var currTime = new Date().getTime();
var timeToCall = Math.max(0, t - (currTime - lastTime));
var id = window.setTimeout(function(){ callback(currTime + timeToCall); }, timeToCall);
lastTime = currTime + timeToCall;
return id;
}
var canvas = document.getElementById("canvas");
var context = canvas.getContext("2d");
canvas.width = window.innerWidth - 20;
canvas.height = window.innerHeight - 20;
canvas.style.width = canvas.width + "px";
canvas.style.height = canvas.height + "px";
var circles = [];
var mouse =
{
x: 0,
y: 0
}
function getCoordinates(x, y)
{
return "(" + x + ", " + y + ")";
}
function getRatio(n, d)
{
// prevent division by 0
if (d === 0 || n === 0)
{
return 0;
}
else
{
return n/d;
}
}
function Circle(x,y,d,b,s,c)
{
this.x = x;
this.y = y;
this.diameter = Math.round(d);
this.radius = Math.round(d/2);
this.bounciness = b;
this.speed = s;
this.color = c;
this.deltaX = 0;
this.deltaY = 0;
this.drawnPosition = "";
this.fill = function()
{
context.beginPath();
context.arc(this.x+this.radius,this.y+this.radius,this.radius,0,Math.PI*2,false);
context.closePath();
context.fill();
}
this.clear = function()
{
context.fillStyle = "#ffffff";
this.fill();
}
this.draw = function()
{
if (this.drawnPosition !== getCoordinates(this.x, this.y))
{
context.fillStyle = this.color;
// if commented, the circle will be drawn if it is in the same position
//this.drawnPosition = getCoordinates(this.x, this.y);
this.fill();
}
}
this.keepInBounds = function()
{
if (this.x < 0)
{
this.x = 0;
this.deltaX *= -1 * this.bounciness;
}
else if (this.x + this.diameter > canvas.width)
{
this.x = canvas.width - this.diameter;
this.deltaX *= -1 * this.bounciness;
}
if (this.y < 0)
{
this.y = 0;
this.deltaY *= -1 * this.bounciness;
}
else if (this.y+this.diameter > canvas.height)
{
this.y = canvas.height - this.diameter;
this.deltaY *= -1 * this.bounciness;
}
}
this.followMouse = function()
{
// deltaX/deltaY will currently cause the circles to "orbit" around the cursor forever unless it hits a wall
var centerX = Math.round(this.x + this.radius);
var centerY = Math.round(this.y + this.radius);
if (centerX < mouse.x)
{
// circle is to the left of the mouse, so move the circle to the right
this.deltaX += this.speed;
}
else if (centerX > mouse.x)
{
// circle is to the right of the mouse, so move the circle to the left
this.deltaX -= this.speed;
}
else
{
//this.deltaX = 0;
}
if (centerY < mouse.y)
{
// circle is above the mouse, so move the circle downwards
this.deltaY += this.speed;
}
else if (centerY > mouse.y)
{
// circle is under the mouse, so move the circle upwards
this.deltaY -= this.speed;
}
else
{
//this.deltaY = 0;
}
this.x += this.deltaX;
this.y += this.deltaY;
this.x = Math.round(this.x);
this.y = Math.round(this.y);
}
}
function getRandomDecimal(min, max)
{
return Math.random() * (max-min) + min;
}
function getRoundedNum(min, max)
{
return Math.round(getRandomDecimal(min, max));
}
function getRandomColor()
{
// array of three colors
var colors = [];
// go through loop and add three integers between 0 and 255 (min and max color values)
for (var i = 0; i < 3; i++)
{
colors[i] = getRoundedNum(0, 255);
}
// return rgb value (RED, GREEN, BLUE)
return "rgb(" + colors[0] + "," + colors[1] + ", " + colors[2] + ")";
}
function createCircle(i)
{
// diameter of circle
var minDiameter = 25;
var maxDiameter = 50;
// bounciness of circle (changes speed if it hits a wall)
var minBounciness = 0.2;
var maxBounciness = 0.65;
// speed of circle (how fast it moves)
var minSpeed = 0.3;
var maxSpeed = 0.45;
// getRoundedNum returns a random integer and getRandomDecimal returns a random decimal
var x = getRoundedNum(0, canvas.width);
var y = getRoundedNum(0, canvas.height);
var d = getRoundedNum(minDiameter, maxDiameter);
var c = getRandomColor();
var b = getRandomDecimal(minBounciness, maxBounciness);
var s = getRandomDecimal(minSpeed, maxSpeed);
// create the circle with x, y, diameter, bounciness, speed, and color
circles[i] = new Circle(x,y,d,b,s,c);
}
function makeCircles()
{
var maxCircles = getRoundedNum(2, 5);
for (var i = 0; i < maxCircles; i++)
{
createCircle(i);
}
}
function drawCircles()
{
var ii = 0;
for (var i = 0; ii < circles.length; i++)
{
if (circles[i])
{
circles[i].draw();
ii++;
}
}
}
function clearCircles()
{
var ii = 0;
for (var i = 0; ii < circles.length; i++)
{
if (circles[i])
{
circles[i].clear();
ii++;
}
}
}
function updateCircles()
{
var ii = 0;
for (var i = 0; ii < circles.length; i++)
{
if (circles[i])
{
circles[i].keepInBounds();
circles[i].followMouse();
ii++;
}
}
}
function update()
{
requestMyAnimationFrame(update,10);
updateCircles();
}
function draw()
{
requestMyAnimationFrame(draw,1000/60);
context.clearRect(0,0,canvas.width,canvas.height);
drawCircles();
}
window.addEventListener("load", function()
{
window.addEventListener("mousemove", function(e)
{
mouse.x = e.layerX || e.offsetX;
mouse.y = e.layerY || e.offsetY;
});
makeCircles();
update();
draw();
});