Why is this html5 canvas animation so intensive? - javascript

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>

Related

Draw Heart using JavaScript in any postion(X,Y)

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>

detecting a click inside a hexagon drawn using 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

fillRect() doesn't apply on stroke()

I have the following code:
socket.on('render', function(data) {
world.fromJSON(data);
me.fromJSON(world.getPlayerById(me.id));
// Drawing:
ctx.fillStyle = "#000000";
ctx.fillRect(0,0,world.WIDTH, world.HEIGHT);
camX = clamp(me.x - $canvas.width()/2, 0, world.WIDTH - $canvas.width());
camY = clamp(me.y - $canvas.height()/2, 0, world.HEIGHT - $canvas.height());
ctx.save();
ctx.translate( -camX, -camY );
background.draw(world.WIDTH, world.HEIGHT, ctx);
world.render(ctx);
ctx.restore();
});
and background which looks like this:
var Background = function() {
this.image;
this.draw = function(WIDTH, HEIGHT, ctx) {
var p = 20;
for (var x = 0; x <= WIDTH; x += 40) {
ctx.moveTo(0.5 + x + p, p);
ctx.lineTo(0.5 + x + p, HEIGHT + p);
}
for (var x = 0; x <= HEIGHT; x += 40) {
ctx.moveTo(p, 0.5 + x + p);
ctx.lineTo(WIDTH + p, 0.5 + x + p);
}
ctx.strokeStyle = "white";
ctx.stroke();
}
}
The problem is that strokes are not overdrawn with the following:
ctx.fillStyle = "#000000";
ctx.fillRect(0,0,world.WIDTH, world.HEIGHT);
I don't know what could cause the problem, but it's very annoying.
and it looks like this
any ideas how to fix this?

Javascript Animation in HTML Canvas - Can't keep animation within borders

I am working with a javascript animation that shows ripples in water in html canvas.
This is the javascript code and the jfiddle link
(function(){
var canvas = document.getElementById('c'),
/** #type {CanvasRenderingContext2D} */
ctx = canvas.getContext('2d'),
width = 400,
height = 400,
half_width = width >> 1,
half_height = height >> 1,
size = width * (height + 2) * 2,
delay = 30,
oldind = width,
newind = width * (height + 3),
riprad = 3,
ripplemap = [],
last_map = [],
ripple,
texture,
line_width = 20,
step = line_width * 2,
count = height / line_width;
canvas.width = width;
canvas.height = height;
with (ctx) {
fillStyle = '#a2ddf8';
fillRect(0, 0, width, height);
fillStyle = '#07b';
save();
rotate(-0.785);
for (var i = 0; i < count; i++) {
fillRect(-width, i * step, width * 3, line_width);
}
restore();
}
texture = ctx.getImageData(0, 0, width, height);
ripple = ctx.getImageData(0, 0, width, height);
for (var i = 0; i < size; i++) {
last_map[i] = ripplemap[i] = 0;
}
/**
* Main loop
*/
function run() {
newframe();
ctx.putImageData(ripple, 0, 0);
}
/**
* Disturb water at specified point
*/
function disturb(dx, dy) {
dx <<= 0;
dy <<= 0;
for (var j = dy - riprad; j < dy + riprad; j++) {
for (var k = dx - riprad; k < dx + riprad; k++) {
ripplemap[oldind + (j * width) + k] += 128;
}
}
}
/**
* Generates new ripples
*/
function newframe() {
var a, b, data, cur_pixel, new_pixel, old_data;
var t = oldind; oldind = newind; newind = t;
var i = 0;
// create local copies of variables to decrease
// scope lookup time in Firefox
var _width = width,
_height = height,
_ripplemap = ripplemap,
_last_map = last_map,
_rd = ripple.data,
_td = texture.data,
_half_width = half_width,
_half_height = half_height;
for (var y = 0; y < _height; y++) {
for (var x = 0; x < _width; x++) {
var _newind = newind + i, _mapind = oldind + i;
data = (
_ripplemap[_mapind - _width] +
_ripplemap[_mapind + _width] +
_ripplemap[_mapind - 1] +
_ripplemap[_mapind + 1]) >> 1;
data -= _ripplemap[_newind];
data -= data >> 5;
_ripplemap[_newind] = data;
//where data=0 then still, where data>0 then wave
data = 1024 - data;
old_data = _last_map[i];
_last_map[i] = data;
if (old_data != data) {
//offsets
a = (((x - _half_width) * data / 1024) << 0) + _half_width;
b = (((y - _half_height) * data / 1024) << 0) + _half_height;
//bounds check
if (a >= _width) a = _width - 1;
if (a < 0) a = 0;
if (b >= _height) b = _height - 1;
if (b < 0) b = 0;
new_pixel = (a + (b * _width)) * 4;
cur_pixel = i * 4;
_rd[cur_pixel] = _td[new_pixel];
_rd[cur_pixel + 1] = _td[new_pixel + 1];
_rd[cur_pixel + 2] = _td[new_pixel + 2];
}
++i;
}
}
}
canvas.onmousemove = function(/* Event */ evt) {
disturb(evt.offsetX || evt.layerX, evt.offsetY || evt.layerY);
};
setInterval(run, delay);
// generate random ripples
var rnd = Math.random;
setInterval(function() {
disturb(rnd() * width, rnd() * height);
}, 700);
})();
The issue is the ripple spills over from one side of the canvas and animates on the opposite side, I want to keep them from moving into the opposite sides.
Thanks!
Stopping waves at edges
To stop the propagation of the waves across the edges you need to limit the function that adds the disturbance so that it does not write past the edges.
So change...
function disturb(dx, dy) {
dx <<= 0;
dy <<= 0;
for (var y = dy - riprad; y < dy + riprad; y++) {
for (var x = dx - riprad; x < dx + riprad; x++) {
ripplemap[oldind + (y * width) + x] += 128;
}
}
}
to...
function disturb(dx, dy) {
dx <<= 0;
dy <<= 0;
for (var y = dy - riprad; y < dy + riprad; y++) {
for (var x = dx - riprad; x < dx + riprad; x++) {
// don't go past the edges.
if(y >= 0 && y < height && x >= 0 && x < width){
ripplemap[oldind + (y * width) + x] += 128;
}
}
}
}
And you also need to change the wave propagation that is in the function newFrame. The propagation is controlled by the value in data. A value of zero means no wave and no propagation. So test if the pixel is on the edge. If the pixel is an edge pixel then just stop the wave by setting data to zero. To save CPU cycles I have added to vars h, w that are height - 1 and width - 1 so you don't have to perform the subtraction twice for every pixel.
Change the code in the function newFrame from...
for (var y = 0; y < _height; y++) {
for (var x = 0; x < _width; x++) {
var _newind = newind + i, _mapind = oldind + i;
data = (
_ripplemap[_mapind - _width] +
_ripplemap[_mapind + _width] +
_ripplemap[_mapind - 1] +
_ripplemap[_mapind + 1]) >> 1;
}
To...
var w = _width - 1; // to save having to subtract 1 for each pixel
var h = _height - 1; // dito
for (var y = 0; y < _height; y++) {
for (var x = 0; x < _width; x++) {
var _newind = newind + i, _mapind = oldind + i;
// is the pixel on the edge
if(y === 0 || x === 0 || y === h || x === w){
data = 0; // yes edge pixel so stop propagation.
}else{
// not on the edge so just do as befor.
data = (
_ripplemap[_mapind - _width] +
_ripplemap[_mapind + _width] +
_ripplemap[_mapind - 1] +
_ripplemap[_mapind + 1]) >> 1;
}
This is not perfect as it will dampen the waves bouncing from the sides but you don't have much CPU time so it is a good solution.
Some notes.
Use requestAnimationFrame rather than setInterval(run, delay) to time the animation as you will cause some devices to crash the page with the code you currently have, if the device can not handle the CPU load. requestAnimationFrame will stop this happening.
Change the run function to
function run() {
newframe();
ctx.putImageData(ripple, 0, 0);
requestAnimationFrame(run);
}
At bottom of code remove setInterval(run,delay); and add
requestAnimationFrame(run);
And change the second setInterval to
var drops = function() {
disturb(rnd() * width, rnd() * height);
setTimeout(drops,700)
};
drops();
NEVER use setInterval, especially for this type of CPU intensive code. You will cause many machines to crash the page. Us setTimeout or requestAnimationFrame instead.
Also remove the with statement where you create the texture.

how to create bezier curve in JavaScript?

I am trying to create a bezier curve using JavaScript on a HTML5 canvas. Below is the code that I have written us in the drawbeziercurve function. The result is that, I only get the four points, but cant get the bezier curve to appear. Any help guys?
function drawBezierCurve() {
"use strict";
var t, i, x, y, x0, x1, x2, x3;
// for (t = 1; t <= nSteps; t++) {
//t = 1/nSteps
q0 = CalculateBezierPoint(0, x0, x1, x2, x3);
for(i = 0; i <= nSteps; i++)
{
t = i / nSteps;
q1 = CalculateBezierPoint(t, x0, x1, x2, x3);
DrawLine(q0, q1);
q0 = q1;
}
//[x] = (1-t)³x0 + 3(1-t)²tx1+3(1-t)t²x2+t³x3
//[y] = (1-t)³y0 + 3(1-t)²ty1+3(1-t)t²y2+t³y3
procedure draw_bezier(A, v1, v2, D)
B = A + v1
C = D + v2
//loop t from 0 to 1 in steps of .01
for(t=0; t <=1; t+ 0.1){
a = t
b = 1 - t
point = A*b³ + 3*B*b²*a + 3C*b*a2 + D*a³
//drawpixel (point)
drawLine(arrayX[0], arrayY[0], (arrayX[0] + arrayX[1] + arrayX[2] + arrayX[3]) / 4,
(arrayY[0] + arrayY[1] + arrayY[2] + arrayY[3]) / 4, 'blue');
//end of loop
}
end of draw_bezier
/* drawLine(arrayX[0], arrayY[0], (arrayX[0] + arrayX[1] + arrayX[2] + arrayX[3]) / 4,
(arrayY[0] + arrayY[1] + arrayY[2] + arrayY[3]) / 4, 'blue');
drawLine((arrayX[0] + arrayX[1] + arrayX[2] + arrayX[3]) / 4,
(arrayY[0] + arrayY[1] + arrayY[2] + arrayY[3]) / 4, arrayX[3], arrayY[3], 'blue'); */
}
// points array
Do draw a poly-line (a line consisting of many points) on a canvas, do something like this:
var canvas = document.querySelector('canvas'),
ctx = canvas.getContext('2d');
ctx.lineWidth = 1.5;
ctx.strokeStyle = 'red';
ctx.beginPath();
for (var i = 0; i < 100; i++)
{
var x = Math.random() * 100,
y = Math.random() * 100;
ctx.lineTo(x, y);
}
ctx.stroke();
You can use something similar, however instead of choosing random x and y values, use your own logic.
Here's a working example on jsFiddle.

Categories