Edit path-clip to fill svg in right way [closed] - javascript
Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 3 years ago.
Improve this question
How to correctly register clip-path so that the line is filled correctly that is, along the line and not from top to bottom as now. The screenshots show an example of filling in. On codepen, you can see the entire svg and see how the animation works (it is controlled by scrolling).
Screenshots:
Screenshot with problem:
Now clip-path its (For all code check codepen):
g.setAttribute('style', `clip-path: polygon(0 0%, 100% 0%, 100% ${progress+0.8}%, 0% ${progress+0.8}%);`)
UPDATE:
Please if you offer a solution show it on my example. Since my problem is specific and most just write without looking completely at the problem on codepen.
For each of your coloured lines, create a path that follows the course of that line back and forth. It's stroke width should be wide enough to cover the line at its widest.
You then use that new line as a <mask> to reveal your colours.
The basic idea is described in my answer to a similar question. That question was about simulating animating handwriting. But the same approach will solve your problem also.
https://stackoverflow.com/a/37787761/1292848
Update
Did you check my codepen before answer?
Yes I did. The technique described there will work perfectly fine for your situation. To prove it, here is my own CodePen:
https://codepen.io/PaulLeBeau/pen/RwNOaZg
This is a proof of concept. I have only created a mask path for a short section of the paths. And to demo the concept, I have used a simple CSS animation, instead of implementing an onscroll handler.
It should be fairly obvious what's happening. You will just need to change your onscroll handler to set the stroke-dashoffset based on how far you have scrolled.
If you don't understand how the stroke-offset effect works to animat a line length, then there are plenty of tutorials all over the web, and here on Stack Overflow. For example, CSS-Tricks has a pretty good one:
https://css-tricks.com/svg-line-animation-works/
I have spent quite some time looking into this (few hours) and it is not so simple to solve. BUT I do have a solution. That said, the solution is also not so simple.
Note, the blue polygon is just there to help explain what shape is being used for the clip-path of the group element <g id="Line_Orange" >
I did not attach this to the scroll event because I needed a way to work on the logic behind the clip-path polygon with the simplest set-up I could get and allow me to start and stop the setInterval() to console.log() out the current x and y values etc. to compute where the turns needed to occur etc.
The full example is here: https://codepen.io/Alexander9111/pen/eYmbajB
And the JavaScript is:
var running = false;
const g = document.querySelector("#Line_Orange");
const g_clone = g.cloneNode(true);
const svg = document.querySelector("svg");
g_clone.id = "Line_Grey";
//svg.appendChild(g_clone);
svg.insertBefore(g_clone, svg.childNodes[0]);
g.setAttribute('clip-path', "polygon(0 0, 0 100, 250 100, 250 0)");
const polygon = document.querySelector("#polygon_mask");
let segment_num = 0;
var temp_arr = [];
var polygon_points_arr = [80, 0, 80, 0];
var polygon_points_str = "80 0 80 0";
const polygon_segments = [
{
n: 0, dir: 1, progress: "y", boost: 1, init_x_y: [80,0], index: [3,5],
points:[80, 0, 80, 250, 250, 250, 250, 0]
},
{
n: 1, dir: 1, progress: "y", boost: 2, init_x_y: [80,100], index: [3,null],
points:[80, 0, 80, 250, 250, 100, 250, 0]
},
{
n: 2, dir: 1, progress: "x", boost: 2, init_x_y: [80,250], index: [4,null],
points:[80, 0, 80, 250, 250, 250, 250, 100, 250, 100, 250, 0]
},
{
n: 3, dir: 1, progress: "x", boost: 1, init_x_y: [250,100], index: [4,6],
points:[80, 0, 80, 250, 450, 250, 450, 100, 250, 100, 250, 0]
},
{
n: 4, dir: 1, progress: "x", boost: 2, init_x_y: [700,100], index: [null,6],
points:[80, 0, 80, 250, 700, 250, 820, 100, 250, 100, 250, 0]
},
{
n: 5, dir: 1, progress: "y", boost: 2, init_x_y: [820,100], index: [null,9],
points:[80, 0, 80, 250, 700, 250, 700, 250, 820, 100, 820, 100, 250, 100, 250, 0]
},
{
n: 6, dir: 1, progress: "y", boost: 1, init_x_y: [820,250], index: [7,9],
points:[80, 0, 80, 250, 700, 250, 700, 250, 820, 100, 820, 100, 250, 100, 250, 0]
},
{
n: 7, dir: 1, progress: "y", boost: 2, init_x_y: [820,600], index: [null,9],
points:[80, 0, 80, 250, 700, 250, 700, 600, 820, 600, 820, 100, 250, 100, 250, 0]
},
{
n: 8, dir: -1, progress: "x", boost: 2, init_x_y: [820,750], index: [null,10],
points:[80, 0, 80, 250, 700, 250, 700, 600, 700, 600, 820, 750, 820, 750, 820, 100, 250, 100, 250, 0]
},
{
n: 9, dir: -1, progress: "x", boost: 1, init_x_y: [700,750], index: [8,10],
points:[80, 0, 80, 250, 700, 250, 700, 600, 700, 600, 820, 750, 820, 750, 820, 100, 250, 100, 250, 0]
},
{
n: 10, dir: -1, progress: "x", boost: 2, init_x_y: [150,600], index: [10,null],
points:[80, 0, 80, 250, 700, 250, 700, 600, 150, 600, 150, 600, 150, 750, 150, 750, 820, 750, 820, 100, 250, 100, 250, 0]
},
{
n: 11, dir: 1, progress: "y", boost: 2, init_x_y: [0,600], index: [11,null],
points:[80, 0, 80, 250, 700, 250, 700, 600, 0, 600, 0, 600, 150, 750, 150, 750, 820, 750, 820, 100, 250, 100, 250, 0]
},
{
n: 12, dir: 1, progress: "y", boost: 1, init_x_y: [0,750], index: [11,13],
points:[80, 0, 80, 250, 700, 250, 700, 600, 0, 600, 0, 600, 150, 750, 150, 750, 820, 750, 820, 100, 250, 100, 250, 0]
}
];
var progressY = 0;
var progressX = 80;
const velocity = 1;
var boost = 1;
var direction = 1;
var timeInterval; //to be started at a later time
function myTimer() {
//console.log(progress);
direction = polygon_segments[segment_num].dir;
polygon_points_arr = polygon_segments[segment_num].points;
//console.log("null == 0", null == 0);
var progress = polygon_segments[segment_num].progress == "x" ? progressX : progressY;
var first_index = polygon_segments[segment_num].index[0];
var second_index = polygon_segments[segment_num].index[1];
if (first_index != null){
polygon_points_arr[first_index] = progress;
}
if (second_index != null){
polygon_points_arr[second_index] = progress;
}
polygon_points_arr.map((child, index) => {
if (index % 2 == 0 && index < polygon_points_arr.length - 1){
return child + ",";
} else {
return child
}
});
temp_arr = polygon_points_arr.map((el, index, arr) => {
if ((index + 1) % 2 == 0 && index < arr.length - 1){
return el + ",";
} else {
return el;
}
});
polygon_points_str = temp_arr.join(" ");
console.log(polygon_points_str);
function incrementAndSetValues(){
segment_num +=1;
boost = polygon_segments[segment_num].boost;
progressX = polygon_segments[segment_num].init_x_y[0];
progressY = polygon_segments[segment_num].init_x_y[1];
}
if (progressY>= 10000) {
clearInterval(timeInterval);
} else {
if (segment_num == 0) {
progressY += (velocity * boost * direction);
if (progressY >= 100) {
incrementAndSetValues()
}
} else if (segment_num == 1){
progressY += (velocity * boost * direction);
if (progressY >= 250) {
incrementAndSetValues()
}
console.log(segment_num);
} else if (segment_num == 2){
progressX += (velocity * boost * direction);
if (progressX >= 250) {
incrementAndSetValues()
}
} else if (segment_num == 3){
progressX += (velocity * boost * direction);
if (progressX >= 700) {
incrementAndSetValues()
}
} else if (segment_num == 4){
progressX += (velocity * boost * direction);
if (progressX >= 820) {
incrementAndSetValues()
}
} else if (segment_num == 5){
progressY += (velocity * boost * direction);
if (progressY >= 250) {
incrementAndSetValues()
}
} else if (segment_num == 6){
progressY += (velocity * boost * direction);
if (progressY >= 600) {
incrementAndSetValues()
}
} else if (segment_num == 7){
progressY += (velocity * boost * direction);
if (progressY >= 750) {
incrementAndSetValues()
}
} else if (segment_num == 8){
progressX += (velocity * boost * direction);
if (progressX <= 700) {
incrementAndSetValues()
}
} else if (segment_num == 9){
progressX += (velocity * boost * direction);
if (progressX <= 150) {
incrementAndSetValues()
}
} else if (segment_num == 10){
progressX += (velocity * boost * direction);
if (progressX <= 0) {
incrementAndSetValues()
}
} else if (segment_num == 11){
progressY += (velocity * boost * direction);
if (progressY >= 750) {
incrementAndSetValues()
}
} else if (segment_num == 12){
progressY += (velocity * boost * direction);
}
}
//console.log(segment_num);
g.setAttribute('clip-path', `polygon(${polygon_points_str})`);
polygon.setAttribute('points', polygon_points_str);
}
function myStopFunction() {
console.log("stop X,Y", progressX, progressY);
document.querySelector("#start").removeAttribute('disabled', true);
document.querySelector("#stop").setAttribute('disabled', true);
clearInterval(timeInterval);
running = false;
}
function myStartFunction() {
timeInterval = setInterval(myTimer, 10);
document.querySelector("#start").setAttribute('disabled', true);
document.querySelector("#stop").removeAttribute('disabled', true);
running = true;
}
document.querySelector("#start").addEventListener('click', myStartFunction);
document.querySelector("#stop").addEventListener('click', myStopFunction);
document.addEventListener('keydown', function(e){
console.log(e.code);
if (e.code == "Enter"){
if (running){
myStopFunction();
} else {
myStartFunction();
}
}
});
document.querySelector("#reset").addEventListener('click', function(){
progressY = 0.00;
progressX = 0.00;
segment_num = 0;
myTimer();
document.querySelector("#start").removeAttribute('disabled', true);
document.querySelector("#stop").removeAttribute('disabled', true);
});
document.addEventListener('DOMContentLoaded',
function(){
const g_grey = document.querySelector("#Line_Grey");
//console.log(g_grey);
const grey_paths = g_grey.querySelectorAll("path, polygon");
for (i = 0; i< grey_paths.length; i++) {
//console.log(grey_paths[i]);
if (grey_paths[i].getAttribute('fill') == "none"){
//do nothing
} else if (grey_paths[i].getAttribute('fill') != "#bbbbbb"){
//must be orange, change to grey
grey_paths[i].setAttribute('fill',"#bbbbbb");
}
}
myTimer();
}, false);
And the most important part of the JavaScript is this array:
const polygon_segments = [
{
n: 0, dir: 1, progress: "y", boost: 1, init_x_y: [80,0], index: [3,5],
points:[80, 0, 80, 250, 250, 250, 250, 0]
}, ...
There is a segment for each segment of the polygon, as it grows and becomes more complex.
This diagram should help explain that a little bit:
And this one, explaining how the polygon increases in the number of points:
Related
Difference between NodeJS Fabric and browser canvas render
I am using exact same javascript code for NodeJS and HTML file with Javascript you can see the code here https://jsfiddle.net/4th8poa9/ fabric.Object.prototype.set({ cornerSize: 22, borderColor: 'rgba(205,205,205,0.5)', centeredRotation: true, centeredScaling: true, rotatingPointOffset: 0, }); fabric.Object.prototype.originX = fabric.Object.prototype.originY = 'center'; fabric.Object.prototype.transparentCorners = false; var canvas = new fabric.Canvas('stage', { width: 1288, height: 1288, preserveObjectStacking: true, controlsAboveOverlay: true, enableRetinaScaling: false, }); var data = { "stages": { "SP": { "data": { "objects": [{ "type": "i-text", "originX": "center", "originY": "center", "left": 331.3, "top": 126.2, "width": 131.37, "height": 33.9, "fill": "#FFFFFF", "visible": true, "text": "Text Layer", "fontSize": "30", "fontWeight": "normal", "fontFamily": "Nerko One", "fontStyle": "", "lineHeight": 1.16, "evented": true, "name": "Text Layer", "styles": {} }], "limit_zone": { "width": 191, "height": 254, "top": 109, "left": 260 }, "product_width": 437, "product_height": 437 }, } }, } var j = 1288; var pw = 1288 var editing = { width: 564, height: 751, top: 53, left: 12 }; var limit_zone = {} fabric.util.loadImage('https://core.simpleprint.com/cdn/xx/gildan_64000_black.jpg', function (a) { var i = new fabric.Image(a); i.set({ left: canvas.width / 2, top: (canvas.height) / 2, width: 1288, height: 1288, selectable: false, evented: false, }); limit_zone = new fabric.Rect({ fill: 'transparent', left: ((canvas.width / 2) + editing.left) - (editing.width / 2), top: (((j / 2) + editing.top) - (editing.height / 2)), height: editing.height, width: editing.width, originX: 'left', originY: 'top', stroke: '#ffffff', strokeDashArray: [5, 5], selectable: false, evented: false, visible: true, radius: 0, rx: 0, ry: 0, }); canvas.add(i, limit_zone) let xCenter = data.stages.SP.data.limit_zone !== undefined ? data.stages.SP.data.limit_zone.left + (data.stages.SP.data.limit_zone.width / 2) : 0, yCenter = data.stages.SP.data.limit_zone !== undefined ? data.stages.SP.data.limit_zone.top + (data.stages.SP.data.limit_zone.height / 2) : 0; xCenter = xCenter !== 0 ? (limit_zone.left + (limit_zone.width / 2)) - xCenter : 0; yCenter = yCenter !== 0 ? (limit_zone.top + (limit_zone.height / 2)) - yCenter : 0; var scale = (data.stages.SP.data.product_height ? 1288 / data.stages.SP.data.product_height : 1); var o = extend({ stroke: '', strokeWidth: 0, scaleX: 1, scaleY: 1, angle: 0, skewX: 0, skewY: 0, left: limit_zone.left + (limit_zone.width / 2), top: limit_zone.top + (limit_zone.height / 2) }, data.stages.SP.data.objects[0]); o.top += yCenter; o.left += xCenter; var d = limit_zone, _ops = extend({ left: d.left + (d.width / 2), top: d.top + (d.height / 2), name: o.text ? o.text : 'Sample Text' }, o) var object = new fabric.IText(o.text ? o.text : 'Sample Text', _ops); canvas.add(object); if (scale !== 1) { objs = canvas.getObjects().filter(function (o) { if (o.evented === true) { o.set('active', true); return true } else return false }); if (objs.length === 0) { return false }; var d = new fabric.Group(objs, { scaleX: scale, scaleY: scale, originX: 'center', originY: 'center' }); var e = (limit_zone.width / 2) + limit_zone.left, yl = (limit_zone.height / 2) + limit_zone.top; d.set({ left: e - ((e - d.left) * scale), top: yl - ((yl - d.top) * scale) }) canvas._activeObject = null; d.setCoords() canvas.renderAll() } }, null, { crossOrigin: 'Anonymous' }) function extend() { for (var i = 1; i < arguments.length; i++) for (var key in arguments[i]) if (arguments[i].hasOwnProperty(key)) arguments[0][key] = arguments[i][key]; return arguments[0]; } In the browser fabric renders the canvas as it should be but there is a problem in NodeJS the text object seems to have much more char spacing I am attaching an image what it looks like in NodeJS I am using some calculation to resize the objects I think that the problem is in this piece of code var d = new fabric.Group(objs, { scaleX: scale, scaleY: scale, originX: 'center', originY: 'center' }); It scales the object width and height but it also scales the char spacing. How to prevent this, how to keep the default char spacing ?
speed and slow the ball using the button
Can you show me how to speed up and slow down the speed of the ball using the button when clicked, I used the code but it hasn't worked yet, this.upButton = this.add.sprite(230, 530, 'down-bubble').setInteractive({ cursor: 'pointer' }); this.downButton = this.add.sprite(80, 530, 'up-bubble').setInteractive({ cursor: 'pointer' }); this.input.on('gameobjectup', function (pointer, gameobject) { if (gameobject === this.downButton && this.spinSpeed.timeScale > 100) { this.spinSpeed.timeScale -= 10.1; } else if (gameobject === this.upButton && this.spinSpeed.timeScale < 19.9) { this.spinSpeed.timeScale += 10.1; } }); generateDance() { this.spinSpeed = 0.003; return this.tweens.addCounter({ from: 220, to: 160, duration: 9000, delay: 2000, ease: 'Sine.easeInOut', repeat: -1, yoyo: true }); }
generateBalls() { const hitArea = new Phaser.Geom.Rectangle(0, 0, 32, 32); const hitAreaCallback = Phaser.Geom.Rectangle.Contains; const circle = new Phaser.Geom.Circle(400, 300, 220); const balls = this.add.group(null, { key: 'balls', frame: [0, 1, 5], repeat: 5, setScale: { x: 3, y: 3 }, hitArea: hitArea, hitAreaCallback: hitAreaCallback, }); // console.log(balls.getChildren().length); this.input.on('gameobjectover', function (pointer, gameObject) { document.body.style.cursor = 'pointer'; }); this.input.on('gameobjectout', function (pointer, gameObject) { document.body.style.cursor = 'default'; }); this.upButton = this.add.sprite(230, 530, 'down-bubble').setInteractive({ cursor: 'pointer' }); this.downButton = this.add.sprite(80, 530, 'up-bubble').setInteractive({ cursor: 'pointer' }); this.input.on('gameobjectup', function (pointer, gameobject) { if (gameobject === this.downButton && this.spinSpeed.timeScale > 100) { this.spinSpeed.timeScale -= 10.1; } else if (gameobject === this.upButton && this.spinSpeed.timeScale < 19.9) { this.spinSpeed.timeScale += 10.1; } }); Phaser.Actions.PlaceOnCircle( balls.getChildren(), circle); return balls; } generateDance() { this.spinSpeed = 0.003; // https://phaser.discourse.group/t/how-to-create-key-combo-with-custom-buttons/2622 return this.tweens.addCounter({ from: 220, to: 160, duration: 9000, delay: 2000, ease: 'Sine.easeInOut', repeat: -1, yoyo: true }); } update() { Phaser.Actions.RotateAroundDistance( this.balls.getChildren(), { x: 400, y: 300 }, this.spinSpeed, this.dance.getValue()); this.playerEyes.update(); // this.buttonsspeed.update(); }
preload(): void { this.load.image('sky', './src/games/gewgly/assets/images/sky.png'); this.load.image('up-bubble', './src/games/gewgly/assets/images/up-bubble.png'); this.load.image('down-bubble', './src/games/gewgly/assets/images/down-bubble.png'); this.load.spritesheet('balls', './src/games/gewgly/assets/images/balls.png', { frameWidth: 17, frameHeight: 17 }); } create(): void { this.add.image(400, 300, 'sky'); this.balls = this.generateBalls(); this.dance = this.generateDance(); // this.buttonsspeed(); this.playerEyes = new Eye(this.generateEyeArg(200,135,3.0)); }
the ball does not function as desired when clicked on the desired button, Phaser 3
there seems to be something wrong with the code, one button has to slow down and one button to speed up when I add this code, this.downButton = this.add.image(80, 530, 'up-bubble').setInteractive(); this.upButton = this.add.image(230, 530, 'down-bubble').setInteractive(); this.input.on('gameobjectup', function (pointer, gameobject) { if (gameobject === this.downButton && this.spinSpeed > 0) { this.spinSpeed -= 0.1; } else if (gameobject === this.upButton && this.spinSpeed < 9.9) { this.spinSpeed += 0.1; } }); but, when I add this code between generateBalls (), it doesn't work at all, it doesn't work, generateBalls() { const hitArea = new Phaser.Geom.Rectangle(0, 0, 32, 32); const hitAreaCallback = Phaser.Geom.Rectangle.Contains; const circle = new Phaser.Geom.Circle(400, 300, 220); const balls = this.add.group(null, { key: 'balls', frame: [0, 1, 5], repeat: 5, setScale: { x: 3, y: 3 }, hitArea: hitArea, hitAreaCallback: hitAreaCallback, }); this.downButton = this.add.image(80, 530, 'up-bubble').setInteractive(); this.upButton = this.add.image(230, 530, 'down-bubble').setInteractive(); this.input.on('gameobjectup', function (pointer, gameobject) { if (gameobject === this.downButton && this.spinSpeed > 0) { this.spinSpeed -= 0.1; } else if (gameobject === this.upButton && this.spinSpeed < 9.9) { this.spinSpeed += 0.1; } }); Phaser.Actions.PlaceOnCircle( balls.getChildren(), circle); return balls; } generateDance() { this.spinSpeed = 0.003; return this.tweens.addCounter({ from: 220, to: 160, duration: 9000, delay: 2000, ease: 'Sine.easeInOut', repeat: -1, yoyo: true }); } update() { this.playerEyes.update(); Phaser.Actions.RotateAroundDistance( this.balls.getChildren(), { x: 400, y: 300 }, this.spinSpeed, this.dance.getValue()); } I took the code from the Phaser 3 example this is https://phaser.io/examples/v3/view/tweens/tween-time-scale
now both buttons function properly generateDance() { this.downButton = this.add.image(230, 530, 'up-bubble').setInteractive(); this.upButton = this.add.image( 80, 530, 'down-bubble').setInteractive(); this.spinSpeed = 0.003; this.downButton.on ('pointerdown', (event) => { if (this.spinSpeed < 1) { this.spinSpeed += 0.002; } }); this.upButton.on('pointerdown', (event) => { if (this.spinSpeed > 0 ) { this.spinSpeed -= 0.001; } }); return this.tweens.addCounter({ from: 220, to: 160, duration: 9000, delay: 2000, ease: 'Sine.easeInOut', repeat: -1, yoyo: true }); }
Is there a better way to repeat display multiple images in the same spot?
I'm creating a Minigame using Canvas HTML5 and am trying to display LEDs on top of another image, currently it works like this: Image displayed Currently, the Image does display and change as intended however the function is not initialised straight away (i'm guessing this is due to Javascript running on one core and how the setInterval function works) the code also seems really clunky and long-winded. Is there a better way to achieve looping of these images to form an animation? I intend to add more animations as the minigame is 'idle' and ideally the function controlling the looping of images should be easily broken. var canvas = document.querySelector('canvas'); var context = canvas.getContext('2d'); function drawSafeBuster(imageSources, callback) { var images = {}; var loadedImages = 0; var numImages = 0; // get number of images for (var src in imageSources) { numImages++; } for (var src in imageSources) { images[src] = new Image(); images[src].onload = function () { if (++loadedImages >= numImages) { callback(images); } }; images[src].src = imageSources[src]; } } //Image path variables. var imageSources = { ledPath: './graphics/leds_safe_dial_minigame.png' }; drawSafeBuster(imageSources, function (images) { //Draw initial LED images. context.drawImage(images.ledPath, 2, 0, 115, 100, 850, 300, 120, 100); context.drawImage(images.ledPath, 2, 0, 115, 100, 1015, 300, 120, 100); //LED Animation Loop var ledRepeat = setInterval(function () { context.fillStyle = '#999999'; var ledRepeat1 = setInterval(function () { context.fillRect(850, 300, 120, 45); context.fillRect(1015, 300, 120, 45); context.drawImage(images.ledPath, 2, 0, 115, 100, 850, 300, 120, 100); context.drawImage(images.ledPath, 2, 0, 115, 100, 1015, 300, 120, 100); }, 500); var ledRepeat2 = setInterval(function () { context.fillRect(850, 300, 120, 45); context.fillRect(1015, 300, 120, 45); context.drawImage(images.ledPath, 120, 0, 115, 100, 850, 300, 120, 100); context.drawImage(images.ledPath, 120, 0, 115, 100, 1015, 300, 120, 100); }, 1500); var ledRepeat3 = setInterval(function () { context.fillRect(850, 300, 120, 45); context.fillRect(1015, 300, 120, 45); context.drawImage(images.ledPath, 238, 0, 115, 100, 850, 300, 120, 100); context.drawImage(images.ledPath, 238, 0, 115, 100, 1015, 300, 120, 100); }, 2500); var clearInterval = setInterval(function () { clearInterval(ledRepeat1); clearInterval(ledRepeat2); clearInterval(ledRepeat3); }, 3500); }, 4500); }); }
I would suggest making each LED an object with its own state (on/off). Create a game object to track the game state and current time/tick. (Here's some light reading about game loops) Not sure about your exact requirements, but here's an example of how I would approach something similar. In the ideal world each requestAnimationFrame() is 1/60th of a second ~ 60 frames per second... See the above link for why this may not be case and how to correct for it. I've not used images for LEDs but this could be added to the LED object and used in the draw function. let canvas, c, w, h, TWOPI = Math.PI * 2; canvas = document.getElementById('canvas'); c = canvas.getContext('2d'); w = canvas.width = 600; h = canvas.height = 400; let game = { state: "RUNNING", tick: 0, actors: [] }; //LED object. let LED = function(x, y, hue, radius, on, toggleRate) { this.position = { x: x, y: y }; this.hue = hue; this.radius = radius; this.on = on; this.toggleRate = toggleRate; this.update = function(tick) { if (tick % this.toggleRate === 0) { this.on = !this.on; } }; this.draw = function(ctx) { ctx.beginPath(); ctx.arc(this.position.x, this.position.y, this.radius, 0, TWOPI, false); ctx.fillStyle = `hsl(${this.hue}, ${this.on ? 80 : 20}%, ${this.on ? 70 : 30}%)`; ctx.fill(); ctx.beginPath(); ctx.arc(this.position.x + this.radius / 5, this.position.y - this.radius / 5, this.radius / 3, 0, TWOPI, false); ctx.fillStyle = `hsl(${this.hue}, ${this.on ? 80 : 20}%, ${this.on ? 90 : 50}%)`; ctx.fill(); }; } //create LEDs for (let i = 0; i < 10; i++) { game.actors.push( new LED( 100 + i * 25, 100, i * 360 / 10, 8, Math.random() * 1 > 0.5 ? true : false, Math.floor(Math.random() * 240) + 60 ) ); } function update() { if (game.state === "RUNNING") { //increase game counter game.tick++; //update actors for (let a = 0; a < game.actors.length; a++) { game.actors[a].update(game.tick); } } else { //noop. } } function clear() { c.clearRect(0, 0, w, h); } function draw() { //draw all actors for (let a = 0; a < game.actors.length; a++) { game.actors[a].draw(c); } } function loop() { update(); clear(); draw(); requestAnimationFrame(loop); } canvas.addEventListener('click', function() { if (game.state === "RUNNING") { game.state = "PAUSED"; } else { game.state = "RUNNING"; } console.log(game.state); }); requestAnimationFrame(loop); body { background: #222; } <!doctype html> <html> <head> <meta charset="utf-8"> </head> <body> <canvas id="canvas"></canvas> </body> </html>
For the best quality always render to the canvas via requestAnimationFrame's callback. Rendering using timers can result in flickering and or shearing of the animation. setTimeout or setInterval are throttled by most browsers when the page is not in focus or visible. Nor are the callbacks always on time If timing is important to 1/60th second use performance.now and the time argument passed to the requestAnimationFrame callback. You will never be precisely on time as the animation is only displayed every 1/60th second (16.66666...ms) so draw the next frame of the animation as soon as possible after the time required (see example) From the information you gave in the question I could not workout what your animation looked like so I made something up as an example. Example Uses promise rather than callback when loading media Image has property added that represents the position of sub images (sprites). The function drawSprite takes the imageName, spriteIdx, and locationName to draw a sub image at a location on the canvas. The main render loop is the function renderLoop it waits until media is loaded and then uses the timing to do the animation. The array timing contains an object for each animation event. The object has the time offset of the event, the function to be called on that event, and the arguments passed to the function. The last timing object in this example, just resets the start time to repeat the animation. Note that this example does not check if the browser stops the animation and will cycle through all animations to catch up. requestAnimationFrame(renderLoop); canvas.width = 64; canvas.height = 16; const ctx = canvas.getContext("2d"); var mediaLoaded = false; const images = {}; var currentStage = 0; var startTime; // do not assign a value to this here or the animation may not start loadMedia({ leds: { src: "https://i.stack.imgur.com/g4Iev.png", sprites: [ {x: 0, y: 0, w: 16, h: 16}, // idx 0 red off {x: 16, y: 0, w: 16, h: 16}, // idx 1 red on {x: 0, y: 16, w: 16, h: 16}, // idx 2 orange off {x: 16, y: 16, w: 16, h: 16}, // idx 3 orange on {x: 0, y: 32, w: 16, h: 16}, // idx 4 green off {x: 16, y: 32, w: 16, h: 16}, // idx 5 green on {x: 0, y: 48, w: 16, h: 16}, // idx 6 cyan off {x: 16, y: 48, w: 16, h: 16}, // idx 7 cyan on ] }, }, images) .then(() => mediaLoaded = true); const renderLocations = { a: {x: 0, y: 0, w: 16, h: 16}, b: {x: 16, y: 0, w: 16, h: 16}, c: {x: 32, y: 0, w: 16, h: 16}, d: {x: 48, y: 0, w: 16, h: 16}, }; function loadMedia(imageSources, images = {}) { return new Promise(allLoaded => { var count = 0; for (const [name, desc] of Object.entries(imageSources)) { const image = new Image; image.src = desc.src; image.addEventListener("load",() => { images[name] = image; if (desc.sprites) { image.sprites = desc.sprites } count --; if (!count) { allLoaded(images) } }); count ++; } }); } function drawSprite(imageName, spriteIdx, locName) { const loc = renderLocations[locName]; const spr = images[imageName].sprites[spriteIdx]; ctx.drawImage(images[imageName], spr.x, spr.y, spr.w, spr.h, loc.x, loc.y, loc.w, loc.h); } function drawLeds(sprites) { for(const spr of sprites) { drawSprite(...spr) } } function resetAnimation() { currentStage = 0; startTime += 4500; } const timing = [ {time: 0, func: drawLeds, args: [[["leds", 0, "a"], ["leds", 2, "b"], ["leds", 4, "c"], ["leds", 6, "d"]]]}, {time: 500, func: drawLeds, args: [[["leds", 1, "a"]]]}, {time: 1500, func: drawLeds, args: [[["leds", 0, "a"], ["leds", 3, "b"]]]}, {time: 2000, func: drawLeds, args: [[["leds", 1, "a"]]]}, {time: 3000, func: drawLeds, args: [[["leds", 0, "a"], ["leds", 2, "b"], ["leds", 5, "c"], ["leds", 7, "d"]]]}, {time: 3250, func: drawLeds, args: [[["leds", 1, "d"]]]}, {time: 3500, func: drawLeds, args: [[["leds", 3, "d"]]]}, {time: 3750, func: drawLeds, args: [[["leds", 5, "d"]]]}, {time: 4000, func: drawLeds, args: [[["leds", 7, "d"]]]}, {time: 4250, func: drawLeds, args: [[["leds", 1, "d"]]]}, {time: 4500 - 17, func: resetAnimation, args: []}, ]; function renderLoop(time) { if (mediaLoaded) { if (startTime === undefined) { startTime = time } var offsetTime = time - startTime; const stage = timing[currentStage]; if(offsetTime > stage.time) { currentStage++; stage.func(...stage.args); } } requestAnimationFrame(renderLoop); } canvas { border:1px solid black; } <canvas id="canvas"></canvas> Image used in example
Declaring a variable inside a function return undefine
Here's my code: Javascript: var currency = { coins: 0, golds: 0, orb: 0 }; var resources = { coals: 0, irons: 0, silvers: 0, diamonds: 0 }; var player = { HP: 0, baseHP: 100, weaponHP: 0, ATK: 0, baseATK: 1, weaponATK: 0, DEF: 0, baseDEF: 5, weaponDEF: 0, DEX: 0, baseDEX: 5, weaponDEX: 0, crit: 0, baseCrit: 5, weaponCrit: 0, level: 1, currentEXP: 0, expLeft: 10 }; var unlocked = { shop: false, tab: false, numberOfGolds: false, mine: false, resources: false, throw10Coins: false, woodenSword: false, ironSword: false, hatchet: false, woodenShield: false, spikeShield: false, blade: false, battleAxe: false, broadSword: false, mace: false, bomb: false, angleSword: false, hacker: false }; var weapon = { hatchet: { atk: 2, def: -1, dex: 0, hp: 0, crit: 0 }, woodenSword: { atk: 5, dex: 0, def: 0, hp: 0, crit: 0 }, ironSword: { atk: 10, crit: 5, dex: 0, def: 0, hp: 0 }, blade: { atk: 25, crit: 20, dex: 10, hp: 0, def: 0 }, mace: { atk: 30, def: 5, dex: -1, hp: 0, crit: 0 }, battleAxe: { atk: 50, def: 5, dex: 0, hp: 0, crit: 0 }, broadSword: { atk: 100, def: 20, dex: 0, crit: 0, hp: 0 }, woodenShield: { atk: 0, def: 10, dex: 0, hp: 0, crit: 0 }, spikeShield: { def: 15, atk: 5, dex: 0, crit: 0, hp: 0 }, bomb: { atk: 0, def: 0, crit: 0, hp: 0, dex: -5 }, angleSword: { atk: 500, def: 100, crit: 25, hp: 100, dex: 10 } } function stat(x) { reStat(); player.baseATK = 1; player.baseDEF = 5; player.baseDEX = 5; player.baseHP = 100; player.baseCrit = 5; player.weaponATK = weapon[x].atk; player.weaponDEF = weapon[x].def; player.weaponDEX = weapon[x].dex; player.weaponHP = weapon[x].hp; player.weaponCrit = weapon[x].crit; player.ATK = player.baseATK + player.weaponATK; player.DEF = player.baseDEF + player.weaponDEF; player.DEX = player.baseDEX + player.weaponDEX; player.HP = player.baseHP + player.weaponHP; player.crit = player.baseCrit + player.weaponCrit; document.getElementById('atk').innerHTML = player.baseATK + player.weaponATK; document.getElementById('def').innerHTML = player.baseDEF + player.weaponDEF; document.getElementById('dex').innerHTML = player.baseDEX + player.weaponDEX; document.getElementById('hp').innerHTML = player.baseHP + player.weaponHP; document.getElementById('crit').innerHTML = player.baseCrit + player.weaponCrit + '%'; } function reStat() { player.ATK = 0; player.DEF = 0; player.DEX = 0; player.crit = 0; player.HP = 0; } var gold = { health: 30 } function move(flag) { if (flag == 'true') { player.x += 1 } if (flag == 'false') { void(0) } } function start(place) { if (place == 'goldMine') { canvas = document.getElementById('canvas'); ctx = canvas.getContext('2d'); var player = { x: 0, y: 198, speed: 3 }; var slope = { x: 148, y: 198, up: false } var gold = { x: 200, y: 185, health: 30, mined: false, x1: 240, y1: 185, mined1: false } var time = 30; var canDamage = false; var time = 50; switchQuest(); ctx.font = '15px Monospace'; function draw() { ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillText('\\o/', player.x, player.y); ctx.fillText("__________________", 0, 197); ctx.fillText("/", slope.x, slope.y); ctx.fillText("______________________________________________________", 155, 185); if (!gold.mined) { ctx.fillText("/O\\", gold.x, gold.y); } ctx.fillText("/O\\", gold.x1, gold.y1); player.x += player.speed; if (player.x >= slope.x - 17 && !slope.up) { player.y -= 13; slope.up = true; } if (player.x >= gold.x - 17 && !gold.mined) { player.speed = 0; time -= 1; } if (time <= 0) { stat(); time = 50; gold.health -= player.weaponATK; } if (gold.health == 0) { player.speed = 3; gold.mined = true; } console.log("Gold Health: " + gold.health); console.log("ATK: " + player.ATK); console.log("Time: " + time); requestAnimationFrame(draw) } } requestAnimationFrame(draw) } window.setInterval( function() { document.getElementById('numberOfCoins').innerHTML = 'You got ' + currency.coins + ' coins!'; document.getElementById('numberOfGolds').innerHTML = 'You mined ' + currency.golds + ' golds!'; document.getElementById('numberOfCoals').innerHTML = resources.coals; document.getElementById('numberOfSilvers').innerHTML = resources.silvers; document.getElementById('numberOfIrons').innerHTML = resources.irons; document.getElementById('numberOfDiamonds').innerHTML = resources.diamonds; So here's my problem. In the Javascript part, at the start, I declare player.ATK = 0. But down in the start() function. When I console.log() out the player.ATK, it returns undefined. If I console.log() it ouside the function or inside the console, it normally returns the variable. Can anyone help me with this? Can this be solve using only Javascript? Thanks!
Your start() function is defining a local player var: function start(place) { if (place == 'goldMine') { canvas = document.getElementById('canvas'); ctx = canvas.getContext('2d'); var player = { // <<-- Here x: 0, y: 198, speed: 3 }; Which is effectively hiding the global player variable from this scope. This new player var does not have and ATK property, which is why it appears to be undefined.