Pointing arrow on precise location on donut chart - javascript

I am trying to point to certain parts on a donut chart. The chart is divided into 9 parts.
comArray is the main array which contain all parts. angleArray is the angles in degrees for each part. useArray contains the parts that should be located through the pointer.
My code is working for sometime but then it starts locating on some different point. For example, if pointer needs to point to 3 it locates properly but after 1 hour it will start locating on the 6 part for 3 part.
What I am doing wrong? You can check the working example here: https://bishttech.com/newtest/
$(document).ready(function() {
var comArray = [1, 2, 3, 4, 5, 6, 7, 8, 9]; // main array of all parts
var angleArray = [20, 60, 100, 140, 180, 220, 260, 300, 340]; // angle in degrees for all parts
var useArray = [3, 6, 8, 2]; // pointer should stop on these parts
//console.log(areacount);
var canvas = document.getElementById("chart");
var ctx = canvas.getContext("2d");
function drawMultiRadiantCircle(xc, yc, r, radientColors) {
var partLength = (2 * Math.PI) / radientColors.length;
var start = 0;
var gradient = null;
var startColor = null,
endColor = null;
var startAngle = 0;
for (var i = 0; i < radientColors.length; i++) {
startColor = radientColors[i];
// endColor = radientColors[(i + 1) % radientColors.length];
endColor = adjust(startColor, -30);
// x start / end of the next arc to draw
var xStart = xc + Math.cos(start) * r;
var xEnd = xc + Math.cos(start + partLength) * r;
// y start / end of the next arc to draw
var yStart = yc + Math.sin(start) * r;
var yEnd = yc + Math.sin(start + partLength) * r;
ctx.beginPath();
gradient = ctx.createLinearGradient(xStart, yStart, xEnd, yEnd);
gradient.addColorStop(0, endColor);
gradient.addColorStop(0.5, startColor);
gradient.addColorStop(1.0, endColor);
ctx.strokeStyle = gradient;
ctx.arc(xc, yc, r, start, start + partLength);
ctx.lineWidth = 30;
ctx.stroke();
ctx.closePath();
start += partLength;
startAngle = partLength;
}
}
var someColors = ["#fb4168", "#4efb7f", "#fbf672", "#63e0fb", "#5b5bfb", "#f079fb", "#e0dffb", "#1aa3fb", "#1aa3fb"];
drawMultiRadiantCircle(300, 300, 200, someColors);
// get location of value
function getCurLoc(value) {
for (var i = 0; i < comArray.length; i++) {
if (value == comArray[i]) {
return i;
break;
}
}
}
var startingPnt = 0;
var curangleDegrees = 0;
var prevangleDegrees = 0;
var prevLoc = 0;
var curLoc = 0;
var rotateNum = 0;
var diffLoc = 0;
//function for displaying data on chart
function displayData() {
var maxPnt = useArray.length - 1;
var currentValue = useArray[startingPnt];
var getLoc = getCurLoc(currentValue); // getting location of value in comArray
if (rotateNum == 0) {
curangleDegrees = angleArray[getLoc];
curLoc = getLoc;
} else {
curLoc = getLoc;
diffLoc = curLoc - prevLoc;
curangleDegrees = (diffLoc * 40);
}
$(".img").rotate(curangleDegrees, {
easing: 'easeInExpo'
});
console.log(getLoc);
if (startingPnt == maxPnt) {
startingPnt = 0;
} else {
startingPnt++;
}
prevLoc = curLoc;
prevangleDegrees = curangleDegrees;
rotateNum++;
}
setInterval(function() {
displayData();
}, 10000);
function adjust(color, amount) {
return '#' + color.replace(/^#/, '').replace(/../g, color => ('0' + Math.min(255, Math.max(0, parseInt(color, 16) + amount)).toString(16)).substr(-2));
}
});
.img {
margin: 48px 52px;
position: absolute;
z-index: 1;
}
#container {
width: 50%;
display: block;
margin: 0 auto;
position: relative;
}
#chart {
position: relative;
z-index: 20;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
<div id="container">
<img class='img' src="images/arrowcircle.png" alt="" />
<canvas id="chart" width="800" height="800"></canvas>
</div>

Related

Paper.js How can I make colorful strokeColor?

I have a problem. I can't make a colorful strokeColor. I tried to do like that
p.strokeColor = 'purple'; p.strokeColor = 'red'; and p.strokeColor=['purple','red'].
But nothing helps. Here's an example.
Here's a demo. https://codepen.io/aqua908/pen/rNvyyJj
You are doing fine, only choose different color every time, say according to y
if (y % 2 == 0) {
p.strokeColor = "purple";
} else {
p.strokeColor = "blue";
}
window.onload = function () {
var w = 800; //window.innerWidth
var h = 400; //window.innerHeight
var params = {
height: 0.3,
amplitude: 60,
speedX: 5,
speedY: 5,
speedZ: 5,
frequenceX: 2,
frequenceY: 2,
frequenceZ: 5
};
var gui = new dat.gui.GUI();
var f1 = gui.addFolder("Noise Waves");
f1.add(params, "height", 0.1, 1).step(0.1);
f1.add(params, "amplitude", 0, 150).step(1);
f1.add(params, "speedX", -10, 10).step(0.5);
f1.add(params, "speedY", -10, 10).step(0.5);
f1.add(params, "speedZ", -10, 10).step(0.5);
f1.add(params, "frequenceX", 1, 10);
f1.add(params, "frequenceY", 1, 10);
f1.add(params, "frequenceZ", 1, 10);
f1.open();
var simplex = new SimplexNoise();
var nx = 0;
var ny = 0;
var nz = 0;
var cols = 256;
var rows = 16;
var primitives = [];
paper.install(window);
paper.setup("myCanvas");
for (var y = 0; y < rows; y++) {
var p = new Path();
p.segments = [];
for (var x = 0; x < cols; x++) {
p.add(new Point(2, h / 2));
}
if (y % 2 == 0) {
p.strokeColor = "purple";
} else {
p.strokeColor = "blue";
}
p.strokeWidth = 2;
//p.smooth({ type: 'continuous' })
primitives.push(p);
}
view.onFrame = function (event) {
nx += params.speedX / 1000;
ny += params.speedY / 1000;
nz += params.speedZ / 100;
for (var y = 0; y < rows; y++) {
var p = primitives[y];
for (var x = 0; x < cols; x++) {
var Y =
(h * (1 - params.height)) / 2 +
((h * params.height) / rows) * y +
simplex.noise3D(
nx + (x * params.frequenceX) / 100,
ny + (y * params.frequenceY) / 100,
(nz * params.frequenceZ) / 100
) *
params.amplitude;
p.segments[x].point.x = (w / cols) * x;
p.segments[x].point.y += 0.1 * (Y - p.segments[x].point.y);
}
}
};
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/simplex-noise/2.3.0/simplex-noise.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/paper.js/0.22/paper.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/dat-gui/0.7.0-0/dat.gui.js"></script>
<canvas id="myCanvas" width="400" height="200"></canvas>
Define an array containing the colors outside the for loop.
let colors = ['purple', 'red', 'blue', 'yellow']
In your for loop use the iteration index (in your case y) to pick a color from the array.
p.strokeColor = colors[y % colors.length];
use module % when the number of lines is greater than the length of the colors array.

How to make a smooth car moving animation?

I created a RaceTrack game, where you simply drive a car, collect yellow bonuses and avoid black obstacles.
The problem is that the animation of my car is not smooth ( When u try to go left or right etc. )
Can someone help me understand how can I do a smooth animation when steering a car ?
How could I add a spontanious curves instead of straight road?
Ps. I know my ColissionChecker() functions aren't perfect, but that is a problem for another day.
I don't know why in snippet's Full Page the canvas is very small and you can't see anything.
var canvas = document.querySelector('canvas');
var c = canvas.getContext('2d');
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var LineWidth = 10;
var LineHeight = 80;
var boundaryTopOffset = 5;
var boundaryLeftOffset = 2;
var boundaryPadding = 50;
var boundaryMiddleOffset = 2;
var speed = 50;
let executedTimer = false;
let dateDiff;
let currentScore = 0;
var leftBoundary = [];
var rightBoundary = [];
var middleBoundary = [];
var bonuses = [];
var obstacles = [];
var car = {
x: 1200,
y: 800
}
document.addEventListener('keydown', function(event) {
let key = event.which
if(key === 37) {
car.x -= speed;
} else if(key === 39) {
car.x += speed;
} else if(key === 38) {
car.y -= speed;
} else if(key === 40) {
car.y += speed;
}
})
for (x = 0; x < 8; x++) {
leftBoundary[x] =
{
offset: boundaryLeftOffset + 400,
topOffset: 0,
width: LineWidth,
height: LineHeight,
color: "red"
};
}
for (x = 0; x < 8; x++) {
middleBoundary[x] =
{
offset: boundaryMiddleOffset + 890,
topOffset: 0,
width: LineWidth,
height: LineHeight,
color: "white"
};
}
for (x = 0; x < 8; x++) {
rightBoundary[x] =
{
offset: boundaryLeftOffset + 1400,
topOffset: 0,
width: LineWidth,
height: LineHeight,
color: "red"
};
}
var cycle = 0,
totalCycle = LineHeight + boundaryPadding;
window.requestAnimationFrame(draw);
function draw() {
if(executedTimer == false) {
obstacles.push({x: Math.floor((Math.random() * 1000) + 450), y: 10});
timerStart();
}
drawCanvas(boundaryLeftOffset-2, 0, canvas.width, canvas.height, 'grey');
cycle = (cycle + 4) % totalCycle;
for (boundary of [leftBoundary, rightBoundary, middleBoundary]) {
for (i = 0; i < boundary.length; i++) {
boundary[i].topOffset = cycle + (i-1) * totalCycle;
drawBoundary(boundary[i], boundary[i].color);
}
}
if(dateDiff >= 1000) {
obstacles.push({x: Math.floor((Math.random() * 900) + 490), y: 10});
bonuses.push({x: Math.floor((Math.random() * 900) + 490), y: 10})
}
drawScore();
drawObstacle();
drawBonus();
drawCar();
obstacleColissionChecker();
bonusColissionChecker();
timerCheck();
window.requestAnimationFrame(draw);
}
function drawBoundary(x, elementColor) {
c.fillStyle = elementColor;
c.fillRect(x.offset+100, x.topOffset, x.width, x.height);
}
function drawCanvas(posX, posY, width, height, elementColor) {
c.fillStyle = elementColor;
c.fillRect(posX, posY, width, height);
}
function drawCar() {
c.fillStyle = "blue";
c.fillRect(car.x, car.y, 100, 150);
c.fillStyle = "black";
for(var i = 0; i < 101; i+=100){
c.beginPath();
c.ellipse(car.x + i, car.y + 10, 10, 15, Math.PI, 0, 2 * Math.PI);
c.ellipse(car.x + i, car.y + 140, 10, 15, Math.PI, 0, 2 * Math.PI);
c.fill();
c.closePath();
}
}
function timerStart() {
date1 = new Date();
executedTimer = true;
}
function timerCheck() {
var date2 = new Date();
dateDiff = Math.abs(date1 - date2);
if(dateDiff >= 1000)date1 = date2;
}
function drawScore() {
c.font='25px Verdana';
c.fillStyle = 'hsl('+ 0 +', 100%, 50%)';
c.fillText('Score : ' + currentScore, 100, 80);
}
function drawObstacle() {
c.fillStyle = "#080D23";
for(obstacle of [obstacles]) {
for (i = 0; i < obstacles.length; i++) {
c.fillRect(obstacle[i].x, obstacle[i].y+= 5, 80, 50);
}
}
}
function drawBonus() {
c.fillStyle = "#F2C14A";
for(bonus of [bonuses]) {
for (i = 0; i < bonuses.length; i++) {
c.beginPath();
c.arc(bonuses[i].x, bonuses[i].y+= 5, 20, 0, Math.PI * 2, false);
c.fill();
c.closePath();
}
}
}
function obstacleColissionChecker() {
for (i = 0; i < obstacles.length; i++) {
if(car.y + 20 - obstacles[i]?.y + 20 > 0 && car.y - 20 - obstacles[i]?.y + 20 < 100
&& car.x + 100 - obstacles[i]?.x + 20 > 0 && car.x - 100 - obstacles[i]?.x - 20 < 200) {
currentScore--;
}
}
}
function bonusColissionChecker() {
for (i = 0; i < bonuses.length; i++) {
if(car.y + 20 - bonuses[i]?.y + 20 > 0 && car.y - 20 - bonuses[i]?.y + 20 < 100
&& car.x + 100 - bonuses[i]?.x + 20 > 0 && car.x - 100 - bonuses[i]?.x - 20 < 200) {
currentScore++;
}
}
}
canvas {
border: 1px solid black;
margin: 0 !important;
padding: 0 !important;
}
body {
margin: 0;
}
<canvas></canvas>
In your code the speed is constant.
The car is either moving at that speed or is not moving.
This is the problem : you need to introduce acceleration.
You should car.x += speed on every frame and alter the speed in the key press handler. It would be a good start for you.

Use same animation for multiple elements

I have this working canvas javascript animation but i would like to use it multiple times, currently it's only possible to have one canvas element with the id "stars" and use that one. Could i perhaps add a class for them instead and get the elements class and loop or what would be my best solution for achieving this? I would like to make this work without repeating to much since i could end up using the animation on different pages.
// Settings
var particleCount = 40,
flareCount = 0,
motion = 0.05,
tilt = 0.05,
color = '#00FF7B',
particleSizeBase = 1,
particleSizeMultiplier = 0.5,
flareSizeBase = 100,
flareSizeMultiplier = 100,
lineWidth = 1,
linkChance = 75, // chance per frame of link, higher = smaller chance
linkLengthMin = 5, // min linked vertices
linkLengthMax = 7, // max linked vertices
linkOpacity = 0.25; // number between 0 & 1
linkFade = 90, // link fade-out frames
linkSpeed = 0, // distance a link travels in 1 frame
glareAngle = -60,
glareOpacityMultiplier = 0.4,
renderParticles = true,
renderParticleGlare = true,
renderFlares = false,
renderLinks = false,
renderMesh = false,
flicker = false,
flickerSmoothing = 15, // higher = smoother flicker
blurSize = 0,
orbitTilt = true,
randomMotion = true,
noiseLength = 1000,
noiseStrength = 3;
var canvas = document.getElementById('stars'),
context = canvas.getContext('2d'),
mouse = {
x: 0,
y: 0
},
m = {},
r = 0,
c = 1000, // multiplier for delaunay points, since floats too small can mess up the algorithm
n = 0,
nAngle = (Math.PI * 2) / noiseLength,
nRad = 100,
nScale = 0.5,
nPos = {
x: 0,
y: 0
},
points = [],
vertices = [],
triangles = [],
links = [],
particles = [],
flares = [];
function init() {
var i, j, k;
// requestAnimFrame polyfill
window.requestAnimFrame = (function() {
return window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function(callback) {
window.setTimeout(callback, 1000 / 60);
};
})();
// Size canvas
resize();
mouse.x = canvas.clientWidth / 2;
mouse.y = canvas.clientHeight / 2;
// Create particle positions
for (i = 0; i < particleCount; i++) {
var p = new Particle();
particles.push(p);
points.push([p.x * c, p.y * c]);
}
vertices = Delaunay.triangulate(points);
var tri = [];
for (i = 0; i < vertices.length; i++) {
if (tri.length == 3) {
triangles.push(tri);
tri = [];
}
tri.push(vertices[i]);
}
// Tell all the particles who their neighbors are
for (i = 0; i < particles.length; i++) {
// Loop through all tirangles
for (j = 0; j < triangles.length; j++) {
// Check if this particle's index is in this triangle
k = triangles[j].indexOf(i);
// If it is, add its neighbors to the particles contacts list
if (k !== -1) {
triangles[j].forEach(function(value, index, array) {
if (value !== i && particles[i].neighbors.indexOf(value) == -1) {
particles[i].neighbors.push(value);
}
});
}
}
}
var fps = 15;
var now;
var then = Date.now();
var interval = 1000 / fps;
var delta;
// Animation loop
(function animloop() {
requestAnimFrame(animloop);
now = Date.now();
delta = now - then;
if (delta > interval) {
then = now - (delta % interval);
resize();
render();
}
})();
}
function render() {
if (randomMotion) {
n++;
if (n >= noiseLength) {
n = 0;
}
nPos = noisePoint(n);
}
if (renderParticles) {
// Render particles
for (var i = 0; i < particleCount; i++) {
particles[i].render();
}
}
}
function resize() {
canvas.width = window.innerWidth * (window.devicePixelRatio || 1);
canvas.height = canvas.width * (canvas.clientHeight / canvas.clientWidth);
}
// Particle class
var Particle = function() {
this.x = random(-0.1, 1.1, true);
this.y = random(-0.1, 1.1, true);
this.z = random(0, 4);
this.color = color;
this.opacity = random(0.1, 1, true);
this.flicker = 0;
this.neighbors = []; // placeholder for neighbors
};
Particle.prototype.render = function() {
var pos = position(this.x, this.y, this.z),
r = ((this.z * particleSizeMultiplier) + particleSizeBase) * (sizeRatio() / 1000),
o = this.opacity;
context.fillStyle = this.color;
context.globalAlpha = o;
context.beginPath();
context.fill();
context.closePath();
if (renderParticleGlare) {
context.globalAlpha = o * glareOpacityMultiplier;
context.ellipse(pos.x, pos.y, r * 100, r, (glareAngle - ((nPos.x - 0.5) * noiseStrength * motion)) * (Math.PI / 180), 0, 2 * Math.PI, false);
context.fill();
context.closePath();
}
context.globalAlpha = 1;
};
// Flare class
// Link class
var Link = function(startVertex, numPoints) {
this.length = numPoints;
this.verts = [startVertex];
this.stage = 0;
this.linked = [startVertex];
this.distances = [];
this.traveled = 0;
this.fade = 0;
this.finished = false;
};
// Utils
function noisePoint(i) {
var a = nAngle * i,
cosA = Math.cos(a),
sinA = Math.sin(a),
rad = nRad;
return {
x: rad * cosA,
y: rad * sinA
};
}
function position(x, y, z) {
return {
x: (x * canvas.width) + ((((canvas.width / 2) - mouse.x + ((nPos.x - 0.5) * noiseStrength)) * z) * motion),
y: (y * canvas.height) + ((((canvas.height / 2) - mouse.y + ((nPos.y - 0.5) * noiseStrength)) * z) * motion)
};
}
function sizeRatio() {
return canvas.width >= canvas.height ? canvas.width : canvas.height;
}
function random(min, max, float) {
return float ?
Math.random() * (max - min) + min :
Math.floor(Math.random() * (max - min + 1)) + min;
}
// init
if (canvas) init();
html,
body {
margin: 0;
padding: 0;
height: 100%;
}
body {
background: #000;
background-image: linear-gradient(-180deg, rgba(0, 0, 0, 0.00) 0%, #000000 100%);
}
#stars {
display: block;
position: relative;
width: 100%;
height: 100%;
z-index: 1;
position: absolute;
}
<script src="https://rawgit.com/ironwallaby/delaunay/master/delaunay.js"></script>
<script src="http://requirejs.org/docs/release/2.1.15/minified/require.js"></script>
<canvas id="stars" width="300" height="300"></canvas>
id="identifier" is unique
class="identifier" could be shared by a list of items
As you mention using class could be an option but you'll need to change your code to select all elements by that class before:
$(".identifier").each(function(a,b)
{
// Actions for each element
}
With javascript:
var elementList = document.getElementsByClassName("identifier");
var elementListSize=elementList.length;
for(var i=0;i<elementListSize;i++) {
// Actions for each element (elementList[i])
}

Snake game: how to check if the head collides with its own body

You may have played Snake, a game where you have to eat food to grow and you fail if you collide with the snake's body or certain obstacles. The first part was easy, but the latter seems impossible to achieve.
I have tried to make a for loop check if the last element of my snake array is colliding with its other parts. My condition was like this: if the x position of the last item in my array is bigger than any of the array items' x position, and smaller than their x position plus their width, and so on. That didn't work.
Here's my code :
<!DOCTYPE html>
<html>
<head>
</head>
<body>
<canvas id="myCanvas" width="200px" height="200px" style="border:1px solid black"/>
<script>
var canvas = document.getElementById("myCanvas");
var ctx = canvas.getContext("2d");
var yPos = 20;
var width = 15;
var variable = 1;
var currentDir = 1;
//var xPos = (width+5)*variable;
var xPos = 20;
var myArr = [{myX:xPos,myY:yPos},{myX:xPos,myY:yPos},{myX:xPos,myY:yPos}];
var downPressed = false;
var upPressed = false;
var leftPressed = false;
var rightPressed = false;
var first = [0,20,40,60,80,100,120,140,160,180];
var firstX = Math.floor(Math.random()*10);
var firstY = Math.floor(Math.random()*10);
var okayed = first[firstX];
var notOkayed = first[firstY];
var maths = myArr[myArr.length-1];
function drawFood() {
ctx.beginPath();
ctx.rect(okayed,notOkayed,15,15);
ctx.fillStyle = "red";
ctx.fill();
ctx.closePath();
}
function drawRectangle() {
ctx.clearRect(0,0,200,200);
drawFood();
for(var i = 0;i<myArr.length;i++) {
ctx.beginPath();
ctx.rect(myArr[i].myX,myArr[i].myY,width,15);
ctx.fillStyle = "blue";
ctx.fill();
ctx.closePath();
}
requestAnimationFrame(drawRectangle);
}
setInterval("calledin()",100);
function calledin() {
var secondX = Math.floor(Math.random()*10);
var secondY = Math.floor(Math.random()*10);
var newobj = {myX:myArr[myArr.length-1].myX+20,myY:myArr[myArr.length-1].myY};
var newobjTwo = {myX:myArr[myArr.length-1].myX,myY:myArr[myArr.length-1].myY+20};
var newobjLeft = {myX:myArr[myArr.length-1].myX-20,myY:myArr[myArr.length-1].myY};
var newobjUp = {myX:myArr[myArr.length-1].myX,myY:myArr[myArr.length-1].myY-20};
var okayNewObj = {myX:myArr[1].myX - 20,myY:myArr[1].myY};
if(myArr[myArr.length-1].myX > 180 || myArr[myArr.length-1].myX < 0 || myArr[myArr.length-1].myY > 180 || myArr[myArr.length-1].myY < 0)
{alert("Game Over");window.location.reload();}
if(myArr[myArr.length-1].myX > okayed-5 && myArr[myArr.length-1].myX < okayed+20 && myArr[myArr.length-1].myY < notOkayed+20 &&
myArr[myArr.length-1].myY > notOkayed-5) {
okayed = first[secondX];
notOkayed = first[secondY];
myArr.unshift(okayNewObj);
}
if(currentDir == 1) {
myArr.push(newobj);
myArr.shift();}
if(currentDir == 2) {
myArr.push(newobjTwo);
myArr.shift();
}
if(currentDir == 4) {
myArr.push(newobjLeft);
myArr.shift();
}
if(currentDir == 3) {
myArr.push(newobjUp);
myArr.shift();
}
for(var i = 0;i<myArr.length-2;i++) {
if(myArr[myArr.length-1].myX > myArr[i].myX &&
myArr[myArr.length-1].myX < myArr[i].myX + 15 && myArr[myArr.length-1].myY > myArr[i].myY && myArr[myArr.length-1].myY > myArr[i].myY + 15)
{alert("Game over");window.location.reload();}
}
}
function downed(e) {
if(e.keyCode==40) {if(currentDir != 3) {currentDir = 2;}}
if(e.keyCode==38) {if(currentDir != 2) {currentDir = 3;}}
if(e.keyCode==39) {if(currentDir != 4) {currentDir = 1;}}
if(e.keyCode==37) {if(currentDir != 1) {currentDir = 4;}}
}
function upped(e) {
if(e.keyCode == 40) {downPressed = false;}
}
document.addEventListener("keydown",downed,false);
document.addEventListener("keyup",upped,false);
drawRectangle();
</script>
</body>
</html>
Suppose the snake is represented by an array called snake in which the head is at index snake.length - 1. We have to compare the position of the head against the positions of the body segments at indices 0 through snake.length - 2.
The following code sets okay to false if the snake head has collided with a body segment. Otherwise, okay remains true.
var head = snake[snake.length - 1],
x = head.x,
y = head.y,
okay = true;
for (var i = snake.length - 2; i >= 0; --i) {
if (snake[i].x == x && snake[i].y == y) {
okay = false;
break;
}
}
Below is a snippet in which I have modified your code to clarify the game logic and to simplify many of the calculations.
Instead of working directly with canvas coordinates, I represent each position with the column index x and row index y of a virtual grid cell. This lets us calculate the neighboring grid positions by adding 1 or -1 to x or y. When it comes time to paint the canvas, we multiply the virtual coordinates by the cell size.
I have replaced most of your literal values with variables. For example, instead of setting the canvas dimensions to 200 by 200, we can do this:
canvas.width = numCols * cellSize;
canvas.height = numRows * cellSize;
This lets us change numCols and numRows in one place to resize the whole game grid. All the calculations work out because they evaluate variables instead of using literals.
I altered the key-event handling to recognize the key codes for the W-A-S-D keys in addition to the arrow keys. When the game is embedded in a long web page, as it is here, you'll probably want to use the W-A-S-D keys so that the page doesn't scroll up and down while you're playing.
var canvas,
ctx,
currentDir,
startX = 1,
startY = 1,
startSnakeLength = 3,
snake,
cellSize = 18,
cellGap = 1,
foodColor = '#a2302a',
snakeBodyColor = '#2255a2',
snakeHeadColor = '#0f266b',
numRows = 10,
numCols = 10,
canvasWidth = numCols * cellSize,
canvasHeight = numRows * cellSize;
var food = {};
function placeFood() {
// Find a random location that isn't occupied by the snake.
var okay = false;
while (!okay) {
food.x = Math.floor(Math.random() * numCols);
food.y = Math.floor(Math.random() * numRows);
okay = true;
for (var i = 0; i < snake.length; ++i) {
if (snake[i].x == food.x && snake[i].y == food.y) {
okay = false;
break;
}
}
}
}
function paintCell(x, y, color) {
ctx.fillStyle = color;
ctx.fillRect(x * cellSize + cellGap,
y * cellSize + cellGap,
cellSize - cellGap,
cellSize - cellGap);
}
function paintCanvas() {
ctx.clearRect(0, 0, canvasWidth, canvasHeight);
paintCell(food.x, food.y, foodColor);
var head = snake[snake.length - 1];
paintCell(head.x, head.y, snakeHeadColor);
for (var i = snake.length - 2; i >= 0; --i) {
paintCell(snake[i].x, snake[i].y, snakeBodyColor);
}
}
function updateGame() {
var head = snake[snake.length - 1],
x = head.x,
y = head.y;
// Move the snake.
var tail = snake.shift();
switch (currentDir) {
case 'up':
snake.push(head = { x: x, y: y - 1 });
break;
case 'right':
snake.push(head = { x: x + 1, y: y });
break;
case 'down':
snake.push(head = { x: x, y: y + 1 });
break;
case 'left':
snake.push(head = { x: x - 1, y: y });
break;
}
paintCanvas();
x = head.x;
y = head.y;
// Check for wall collision.
if (x < 0 || x >= numCols || y < 0 || y >= numRows) {
stopGame('wall collision');
return;
}
// Check for snake head colliding with snake body.
for (var i = snake.length - 2; i >= 0; --i) {
if (snake[i].x == x && snake[i].y == y) {
stopGame('self-collision');
return;
}
}
// Check for food.
if (x == food.x && y == food.y) {
placeFood();
snake.unshift(tail);
setMessage(snake.length + ' segments');
}
}
var dirToKeyCode = { // Codes for arrow keys and W-A-S-D.
up: [38, 87],
right: [39, 68],
down: [40, 83],
left: [37, 65]
},
keyCodeToDir = {}; // Fill this from dirToKeyCode on page load.
function keyDownHandler(e) {
var keyCode = e.keyCode;
if (keyCode in keyCodeToDir) {
currentDir = keyCodeToDir[keyCode];
}
}
function setMessage(s) {
document.getElementById('messageBox').innerHTML = s;
}
function startGame() {
currentDir = 'right';
snake = new Array(startSnakeLength);
snake[snake.length - 1] = { x: startX, y: startY };
for (var i = snake.length - 2; i >= 0; --i) {
snake[i] = { x: snake[i + 1].x, y: snake[i + 1].y + 1 };
}
placeFood();
paintCanvas();
setMessage('');
gameInterval = setInterval(updateGame, 200);
startGameButton.disabled = true;
}
function stopGame(message) {
setMessage(message + '<br> ended with ' + snake.length + ' segments');
clearInterval(gameInterval);
startGameButton.disabled = false;
}
var gameInterval,
startGameButton;
window.onload = function () {
canvas = document.getElementById('gameCanvas'),
ctx = canvas.getContext('2d');
canvas.width = numCols * cellSize;
canvas.height = numRows * cellSize;
Object.keys(dirToKeyCode).forEach(function (dir) {
dirToKeyCode[dir].forEach(function (keyCode) {
keyCodeToDir[keyCode] = dir;
})
});
document.addEventListener("keydown", keyDownHandler, false);
startGameButton = document.getElementById('startGameButton');
startGameButton.onclick = startGame;
}
body {
font-family: sans-serif;
}
#gameCanvas {
border: 1px solid #000;
float: left;
margin-right: 15px;
}
#startGameButton, #messageBox {
font-size: 16px;
margin-top: 15px;
}
#messageBox {
line-height: 24px;
}
<canvas id="gameCanvas"></canvas>
<button id="startGameButton">Start game</button>
<div id="messageBox"></div>

Adding grid over Fabric.js canvas

I just started using Fabric.js (I have to say, I'm impressed).
I want to add a grid over the fabric objects. In the following code, I am putting my grid canvas right over on the Fabric canvas. The problem here is that, I now cannot move my fabric objects!
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<script type='text/javascript' src='http://code.jquery.com/jquery-1.7.1.js'></script>
<script type='text/javascript' src='http://cdnjs.cloudflare.com/ajax/libs/fabric.js/1.2.0/fabric.all.min.js'></script>
</head>
<body>
<div style="height:480px;width:640px;border:1px solid #ccc;position:relative;font:16px/26px Georgia, Garamond, Serif;overflow:auto;">
<canvas id="rubber" width="800" height="800"
style="position: absolute; left: 0; top: 0; z-index: 0;"></canvas>
<canvas id="myCanvas" width="800" height="800"
style="position: absolute; left: 0; top: 0; z-index: 1;"></canvas>
</div>
<script>
//<![CDATA[
$(window).load(function(){
$(document).ready(function () {
function renderGrid(x_size,y_size, color)
{
var canvas = $("#myCanvas").get(0);
var context = canvas.getContext("2d");
context.save();
context.lineWidth = 0.5;
context.strokeStyle = color;
// horizontal grid lines
for(var i = 0; i <= canvas.height; i = i + x_size)
{
context.beginPath();
context.moveTo(0, i);
context.lineTo(canvas.width, i);
context.closePath();
context.stroke();
}
// vertical grid lines
for(var j = 0; j <= canvas.width; j = j + y_size)
{
context.beginPath();
context.moveTo(j, 0);
context.lineTo(j, canvas.height);
context.closePath();
context.stroke();
}
context.restore();
}
renderGrid(10,15, "gray");
});
});//]]>
var canvas = new fabric.Canvas('rubber');
canvas.add(new fabric.Circle({ radius: 30, fill: '#f55', top: 100, left: 100 }));
canvas.selectionColor = 'rgba(0,255,0,0.3)';
canvas.selectionBorderColor = 'red';
canvas.selectionLineWidth = 5;
</script>
</body>
</html>
I am hoping that there is a way to do this in Fabric itself.
Any help would be awesome, thanks!
This two lines of code will work:
var gridsize = 5;
for(var x=1;x<(canvas.width/gridsize);x++)
{
canvas.add(new fabric.Line([100*x, 0, 100*x, 600],{ stroke: "#000000", strokeWidth: 1, selectable:false, strokeDashArray: [5, 5]}));
canvas.add(new fabric.Line([0, 100*x, 600, 100*x],{ stroke: "#000000", strokeWidth: 1, selectable:false, strokeDashArray: [5, 5]}));
}
A shorter version and more generic for copy/paste :
var oCanvas; // must be your canvas object
var gridWidth; // <= you must define this with final grid width
var gridHeight; // <= you must define this with final grid height
// to manipulate grid after creation
var oGridGroup = new fabric.Group([], {left: 0, top: 0});
var gridSize = 20; // define grid size
// define presentation option of grid
var lineOption = {stroke: 'rgba(0,0,0,.4)', strokeWidth: 1, selectable:false, strokeDashArray: [3, 3]};
// do in two steps to limit the calculations
// first loop for vertical line
for(var i = Math.ceil(gridWidth/gridSize); i--;){
oGridGroup.add( new fabric.Line([gridSize*i, 0, gridSize*i, gridHeight], lineOption) );
}
// second loop for horizontal line
for(var i = Math.ceil(gridHeight/gridSize); i--;){
oGridGroup.add( new fabric.Line([0, gridSize*i, gridWidth, gridSize*i], lineOption) );
}
// Group add to canvas
oCanvas.add(oGridGroup);
I hope this will help you----
function draw_grid(grid_size) {
grid_size || (grid_size = 25);
currentCanvasWidth = canvas.getWidth();
currentcanvasHeight = canvas.getHeight();
// Drawing vertical lines
var x;
for (x = 0; x <= currentCanvasWidth; x += grid_size) {
this.grid_context.moveTo(x + 0.5, 0);
this.grid_context.lineTo(x + 0.5, currentCanvasHeight);
}
// Drawing horizontal lines
var y;
for (y = 0; y <= currentCanvasHeight; y += grid_size) {
this.grid_context.moveTo(0, y + 0.5);
this.grid_context.lineTo(currentCanvasWidth, y + 0.5);
}
grid_size = grid_size;
this.grid_context.strokeStyle = "black";
this.grid_context.stroke();
}
My solution is -
var width = canvas.width;
var height = canvas.height;
var j = 0;
var line = null;
var rect = [];
var size = 20;
console.log(width + ":" + height);
for (var i = 0; i < Math.ceil(width / 20); ++i) {
rect[0] = i * size;
rect[1] = 0;
rect[2] = i * size;
rect[3] = height;
line = null;
line = new fabric.Line(rect, {
stroke: '#999',
opacity: 0.5,
});
line.selectable = false;
canvas.add(line);
line.sendToBack();
}
for (i = 0; i < Math.ceil(height / 20); ++i) {
rect[0] = 0;
rect[1] = i * size;
rect[2] = width;
rect[3] = i * size;
line = null;
line = new fabric.Line(rect, {
stroke: '#999',
opacity: 0.5,
});
line.selectable = false;
canvas.add(line);
line.sendToBack();
}
canvas.renderAll();
You have to save all line objects for removing grid, or you can added all line objects to a group, and you can remove the group for removing grid, well I think this is not elegant one, but worked.
If you don't insist on generating your grid dynamically you might want to consider the native overlay image function that fabric.js provides.
var canvas = new fabric.Canvas('rubber');
canvas.setOverlayImage('grid.png', canvas.renderAll.bind(canvas));
It won't hinder interactions with the objects on the canvas at all.
I really liked #Draeli's answer, but it seemed it wasn't working with the latest fabric version. Fixed the old one and added one slight adjustment that was necessary for myself - centring the grid.
Anyhow, maybe someone else finds it useful:
const gridSize = 100;
const width = this.canvas.getWidth();
const height = this.canvas.getHeight();
const left = (width % gridSize) / 2;
const top = (height % gridSize) / 2;
const lines = [];
const lineOption = {stroke: 'rgba(0,0,0,1)', strokeWidth: 1, selectable: false};
for (let i = Math.ceil(width / gridSize); i--;) {
lines.push(new fabric.Line([gridSize * i, -top, gridSize * i, height], lineOption));
}
for (let i = Math.ceil(height / gridSize); i--;) {
lines.push(new fabric.Line([-left, gridSize * i, width, gridSize * i], lineOption));
}
const oGridGroup = new fabric.Group(lines, {left: 0, top: 0});
this.canvas.add(oGridGroup);
this.canvas - this supposedly is the fabric instance.
Here is my solution, works with Fabric Js 4.
at the end there is 2 events listeners that append the grid when object moving and remove the grid when object move end. (improvement of #draeli answer https://stackoverflow.com/a/35936606/1727357 )
(function () {
const gcd = (a, b) => {
if (!b) {
return a;
}
return gcd(b, a % b);
}
const gridWidth = canvas.getWidth();
const gridHeight = canvas.getHeight();
const oGridGroup = [];
console.log(gcd(gridWidth, gridHeight));
const gridRows = gcd(gridWidth, gridHeight);
const gridCols = gcd(gridWidth, gridHeight);
const lineOption = { stroke: 'rgba(0,0,0,.1)', strokeWidth: 1, selectable: false, evented: false };
for (let i = 0; i <= gridWidth; i += gridCols) {
oGridGroup.push(new fabric.Line([i, 0, i, gridHeight], lineOption));
}
for (let i = 0; i <= gridHeight; i += gridRows) {
oGridGroup.push(new fabric.Line([0, i, gridWidth, i], lineOption));
}
const theGorup = new fabric.Group(oGridGroup);
theGorup.set({
selectable: false,
evented: false
})
canvas.on('mouse:down', function (event) {
if (event.target) {
canvas.add(theGorup);
}
});
canvas.on('mouse:up', function (event) {
canvas.remove(theGorup);
});
}())
Updated:
Answer is based on the code posted by rafi:
I have updated the missing grid_context.
Kindly replace "<your canvas Id>" with your canvas Id in string. For e.g. "myCanvas".
Usually, any operation you do on the canvas will clear out grid on the canvas, register the after:render event on fabric.js canvas to redraw it. So you can always see it.
var canvas = new fabric.Canvas(<your canvas Id>);
canvas.on('after:render',function(ctx){
draw_grid(25);
});
function draw_grid(grid_size) {
grid_size || (grid_size = 25);
var currentCanvas = document.getElementById(<your canvas Id>);
var grid_context = currentCanvas.getContext("2d");
var currentCanvasWidth = canvas.getWidth();
var currentCanvasHeight = canvas.getHeight();
// Drawing vertical lines
var x;
for (x = 0; x <= currentCanvasWidth; x += grid_size) {
grid_context.moveTo(x + 0.5, 0);
grid_context.lineTo(x + 0.5, currentCanvasHeight);
}
// Drawing horizontal lines
var y;
for (y = 0; y <= currentCanvasHeight; y += grid_size) {
grid_context.moveTo(0, y + 0.5);
grid_context.lineTo(currentCanvasWidth, y + 0.5);
}
grid_context.strokeStyle = "#0000002b";
grid_context.stroke();
}

Categories

Resources