I'm building a video game where a spaceship moves with controllers and it must avoid the fireball in order to continue to play. If it collides into the fireball the game must display "Game Over" and restart.
At the beginning of the game, there is an input where the user puts his name. Then there is a countdown and then the game starts. I would like that the user gets back to the countdown instead of the point where he must input his name. Does someone knows how to do this?
Code for input:
<form id="askName" title="Write your name">
<label> Enter your username: </label>
<input id="input" type="text" maxlength="10" autofocus>
<button type="button" onclick="countDown(); return Username()" id="begin-timer">
Submit
</button>
let icon = document.getElementById("icon")
let fireballElement = document.querySelector("#fireball")
var input = document.getElementById("input")
function Username(field) {
field = input.value
if (field == "") { alert("Complete blanks"); return false }
document.getElementById("askName").style.display = "none"
setTimeout(function() {
document.getElementById("name").innerHTML = "Player: " + field
icon.style.display = 'block'
fireballElement.style.display = "block"
checkCollision()
}, 4000)
}
CountDown
var count = 3
function countDown() {
function preventCountFast() {
document.getElementById("count").innerHTML = count
if (count > 0) { count-- }
else {
clearInterval(ncount);
document.getElementById("count").style.display = "none"
}
}
var ncount = setInterval(preventCountFast, 1000)
}
Detect collision when fireball touches spaceship:
function checkCollision() {
var elem = document.getElementById("icon")
var elem2 = document.getElementById("fireball")
if (detectOverlap(elem, elem2) && elem2.getAttribute('hit') == 'false') {
hits++
elem2.setAttribute('hit', true)
//THIS IS WHERE YOU SHOULD LOOK AT
document.querySelector("#stopGame").style.display = "inline"
}
setTimeout(checkCollision, 20)
}
var detectOverlap = (function() {
function getPositions(elem) {
var pos = elem.getBoundingClientRect()
return [[pos.left, pos.right], [pos.top, pos.bottom]]
}
function comparePositions(p1, p2) {
var r1, r2
r1 = p1[0] < p2[0] ? p1 : p2
r2 = p1[0] < p2[0] ? p2 : p1
return r1[1] > r2[0] || r1[0] === r2[0]
}
return function(a, b) {
var pos1 = getPositions(a), pos2 = getPositions(b)
return comparePositions(pos1[0], pos2[0]) && comparePositions(pos1[1], pos2[1])
}
})()
<img src="Photo/fireball.png" id="fireball" style="display:none>
<img src="Photo/Spaceship1.png" id="icon" style="display:none">
<h2 id="stopGame"> Game Over! </h2>
Fireball movement:
function fFireball(offset) {
return Math.floor(Math.random() * (window.innerWidth - offset))
}
let fireball = { x: fFireball(fireballElement.offsetWidth), y: 0 }
const fireLoop = function() {
fireball.y += 1
fireballElement.style.top = fireball.y + 'px'
if (fireball.y > window.innerHeight) {
fireball.x = fFireball(fireballElement.offsetWidth)
fireballElement.style.left = fireball.x + 'px'
fireball.y = 0
fireballElement.setAttribute('hit', false )
}
}
fireballElement.style.left = fireball.x + 'px'
let fireInterval = setInterval(fireLoop, 1000 / 200)
Spaceship movement:
//Spaceship moves into space + prevent going out borders
let hits = 0
let display = document.getElementById("body")
let rect = icon
let pos = { top: 1000, left: 570 }
const keys = {}
window.addEventListener("keydown", function(e) { keys[e.keyCode] = true })
window.addEventListener("keyup" , function(e) { keys[e.keyCode] = false })
const loop = function() {
if (keys[37] || keys[81]) { pos.left -= 10 }
if (keys[39] || keys[68]) { pos.left += 10 }
if (keys[38] || keys[90]) { pos.top -= 10 }
if (keys[40] || keys[83]) { pos.top += 10 }
var owidth = display.offsetWidth
var oheight = display.offsetHeight
var iwidth = rect.offsetWidth
var iheight = rect.offsetHeight
if (pos.left < 0) pos.left = -10
if (pos.top < 0) pos.top = -10
if (pos.left + iwidth >= owidth ) pos.left = owidth - iwidth
if (pos.top + iheight >= oheight) pos.top = oheight- iheight
rect.setAttribute("data", owidth + ":" + oheight)
rect.style.left = pos.left + "px"; rect.style.top = pos.top + "px"
}
let sens = setInterval(loop, 1000 / 60)
Convert your countDown() to this:
function countDown(count) {
function preventCountFast() {
document.getElementById("count").innerHTML = count
document.getElementById("count").style.display = "block"
if (count > 0) { count-- }
else { clearInterval(ncount); document.getElementById("count").style.display = "none" }
}
var ncount = setInterval(preventCountFast, 1000)
}
So, while calling this, always call it as countDown(3) it will be easier. Next, modify your checkCollision() just a bit:
function checkCollision() {
var elem = document.getElementById("icon")
var elem2 = document.getElementById("fireball")
if (detectOverlap(elem, elem2) && elem2.getAttribute('hit') == 'false') {
hits++
elem2.setAttribute('hit', true)
document.querySelector("#stopGame").style.display = "block"
document.querySelector("#stopGame").style.animation = "seconds 5s forwards"
/* Added these three lines so that countDown() is called again if collision
occurs*/
setTimeout(function () {
countDown(5);
}, 2000);
/* End of edit */
}
setTimeout(checkCollision, 1)
}
Create a restart function and call it at the end of checkCollision like below.
function checkCollision() {
var elem = document.getElementById("icon")
var elem2 = document.getElementById("fireball")
if (detectOverlap(elem, elem2) && elem2.getAttribute('hit') == 'false') {
hits++
elem2.setAttribute('hit', true)
document.querySelector("#stopGame").style.display = "block"
document.querySelector("#stopGame").style.animation = "seconds 5s forwards"
// call restart at end of game
restart();
return;
}
setTimeout(checkCollision, 1)
}
function restart() {
// clear fireball animation
clearInterval(fireInterval);
count = 3;
let countElement = document.getElementById("count");
let stopGame = document.querySelector("#stopGame");
// show game over for 3 seconds to user
setTimeout(function () {
stopGame.style.animation = "";
stopGame.style.display = icon.style.display = fireballElement.style.display =
"none";
countElement.innerHTML = "";
countElement.style.display = "block";
countElement.style.transform = "scale(1)";
// allow count rerender
setTimeout(function () {
fireballElement.setAttribute("hit", false);
countDown();
// wait for count down to be over
setTimeout(function () {
// reset player position
pos.left = display.offsetWidth / 2;
pos.top = display.offsetHeight;
icon.style.display = "block";
// reset fireball position
fireball = { x: fFireball(display.offsetWidth / 2), y: 0 };
fireballElement.style.top = fireball.y + "px";
fireballElement.style.left = fireball.x + "px";
fireballElement.style.display = "block";
clearInterval(sens);
// restart loop list
sens = setInterval(loop, 1000 / 60);
fireInterval = setInterval(fireLoop, 1000 / 200);
checkCollision();
}, 4000);
}, 1000);
}, 3000);
}
Separate logic from presentation
The reason why you encountered this problem is that your code is tightly coupled to the presentation (DOM), and makes use of the global scope.
Your game logic should be separated from the presentation - DOM is basically an I/O device. Otherwise you tie yourself to a particular implementation (imagine refactoring this to a React application or using Material design, etc).
The principle is called "separation of concerns" (or SoC), and is a well-known principle of software design that will serve you well in the future.
Part 1. Counter
If you rely on a global variable like count to launch the timer, you will inevitably encounter issues with resetting it - make the state internal and only pass in configuration (start, end, and what to do on each step).
function getCounter({
init = 3,
onEnd = () => {},
onStep = () => {},
until = 0
} = {}) {
let curr = init;
return {
interval : null,
start() {
this.interval = setInterval(() => {
onStep(this, curr--);
const shouldStop = until === curr;
shouldStop && this.stop();
}, 1e3);
},
stop() {
const { interval } = this;
clearInterval(interval);
onEnd();
}
};
}
const counter = getCounter({
onStep : (counter, curr) => console.log(curr),
onEnd : () => console.log("ended")
});
counter.start();
Part 2. Game Over
Instead of controlling the UI upon collision, control what should happen, and defer coupling to a particular API:
const detectOverlap = () => !!Math.floor( Math.random() * 8 ); //mock for testing
const checkCollision = ({
obj1,
obj2,
interval = 20,
curHits = 0,
maxHits = 0,
onMiss,
onHit,
onGameOver
}) => {
const isHit = detectOverlap(obj1, obj2);
isHit && curHits++;
const hitOrMissConfig = {
curHits,
maxHits,
obj1,
obj2
};
isHit ? onHit(hitOrMissConfig) : onMiss(hitOrMissConfig);
const timeout = setTimeout(
() => checkCollision({
obj1, obj2, interval,
curHits, maxHits,
onHit, onMiss, onGameOver
}),
interval
);
if (curHits >= maxHits) {
clearTimeout(timeout);
return onGameOver();
}
};
const obj1 = { id : 1 };
const obj2 = { id : 2 };
const onHit = () => console.log("hit!");
const onMiss = () => console.log("miss!");
const onGameOver = () => console.log("game over!");
checkCollision({ obj1, obj2, onHit, onMiss, onGameOver, maxHits : 8 });
Part 3. Start logic
Instead of starting the game from the name form, you should encapsulate your logic and call it as a callback - this way you will have control over when to initiate the name form, countdown or anything else:
const loadForm = ({ parent = document.body, defaultUname, onSubmit } = {}) => {
const form = document.createElement("form");
const input = document.createElement("input");
input.name = "name";
input.type = "text";
input.value = defaultUname;
const start = document.createElement("button");
start.type = "button";
start.innerText = "Start";
form.append(input, start);
parent.append(form);
start.addEventListener("click", (event) => {
onSubmit({ name : input.value, firstTime : false });
form.remove();
});
};
//mocks for testing
const checkCollision = () => console.log("checking collision");
const countDown = (init) => {
if(init) {
console.log(init);
setTimeout(() => countDown(--init), 1e3);
}
};
const startGame = ({ name = "Player 1", firstTime = true } = {}) => {
let restarted = false;
if(!restarted && firstTime) {
return loadForm({
defaultUname : name,
onSubmit : startGame
});
}
countDown(3);
};
startGame();
All Steps combined
You will have to implement the UI handling, guards against no name, and connect collision detection, but this should take care of all the core logic. You might also want to make your fireball and spaceship proper JavaScript objects, and not impose logic on DOM elements for the reasons stated above.
function getCounter({
init = 3,
onEnd = () => {},
onStep = () => {},
until = 0
} = {}) {
let curr = init;
return {
interval : null,
start() {
this.interval = setInterval(() => {
onStep(this, curr--);
const shouldStop = until === curr;
shouldStop && this.stop();
}, 1e3);
},
stop() {
const { interval } = this;
clearInterval(interval);
onEnd();
}
};
}
const detectOverlap = () => !!Math.floor(Math.random() * 4); //mock for testing
const checkCollision = ({
obj1,
obj2,
interval = 20,
curHits = 0,
maxHits = 0,
onMiss,
onHit,
onGameOver
}) => {
const isHit = detectOverlap(obj1, obj2);
isHit && curHits++;
const hitOrMissConfig = {
curHits,
maxHits,
obj1,
obj2
};
isHit ? onHit(hitOrMissConfig) : onMiss(hitOrMissConfig);
const timeout = setTimeout(
() => checkCollision({
obj1, obj2, interval,
curHits, maxHits,
onHit, onMiss, onGameOver
}),
interval
);
if (curHits >= maxHits) {
clearTimeout(timeout);
return onGameOver();
}
};
const loadForm = ({
parent = document.body,
defaultUname,
onSubmit
} = {}) => {
const form = document.createElement("form");
const input = document.createElement("input");
input.name = "name";
input.type = "text";
input.value = defaultUname;
const start = document.createElement("button");
start.type = "button";
start.innerText = "Start";
form.append(input, start);
parent.append(form);
start.addEventListener("click", (event) => {
onSubmit({
name: input.value,
firstTime: false
});
form.remove();
});
};
const startGame = ({
name = "Player 1",
firstTime = true
} = {}) => {
if (firstTime) {
return loadForm({
defaultUname: name,
onSubmit: startGame
});
}
console.log(`Get ready, ${name}`);
const counter = getCounter({
onStep : (_,count) => console.log(count),
onEnd : () => checkCollision({
interval : 1e2,
maxHits : 8,
obj1 : { id : 1 },
obj2 : { id : 2 },
onHit : () => console.log("hit!"),
onMiss : () => console.log("miss!"),
onGameOver : () => {
console.log("game over!");
startGame({ name, firstTime : false });
}
})
});
counter.start();
};
startGame();
Useful Resources
How I separate logic from presentation?
Related
I'm having an issue and I don't find the answer by myself.
I'm trying to make the following code work. Actually it doesn't work in my Vue project.
const text = document.getElementById("text");
const phrases = [
"I'm John Doe",
"I'm student",
"I'm developer",
];
let currentPhraseIndex = 0;
let currentCharacterIndex = 0;
let currentPhrase = "";
let isDeleting = false;
function loop() {
const currentPhraseText = phrases[currentPhraseIndex];
if (!isDeleting) {
currentPhrase += currentPhraseText[currentCharacterIndex];
currentCharacterIndex++;
} else {
currentPhrase = currentPhrase.slice(0, -1);
currentCharacterIndex--;
}
text.innerHTML = currentPhrase;
if (currentCharacterIndex === currentPhraseText.length) {
isDeleting = true;
}
if (currentCharacterIndex === 0) {
currentPhrase = "";
isDeleting = false;
currentPhraseIndex++;
if (currentPhraseIndex === phrases.length) {
currentPhraseIndex = 0;
}
}
const spedUp = Math.random() * (80 - 50) + 50;
const normalSpeed = Math.random() * (300 - 200) + 200;
const time = isDeleting ? spedUp : normalSpeed;
setTimeout(loop, time);
}
loop();
<h2 id="text"></h2>
As you can see the code is actually working. Checkout the errors I have in my from my Vue Js project.
Do not hesitate, if you have any suggestions to improve my code according to Vue of course.
Try to put variables in data property and function in methods, or i composition api make variables reactive:
const { ref, reactive, onMounted } = Vue
const app = Vue.createApp({
setup() {
const opt = reactive({
currentPhraseIndex: 0,
currentCharacterIndex: 0,
currentPhrase: "",
isDeleting: false
})
const phrases = reactive([
"I'm John Doe",
"I'm student",
"I'm developer"
])
const text = ref('')
const loop = () => {
const currentPhraseText = phrases[opt.currentPhraseIndex];
if (!opt.isDeleting) {
opt.currentPhrase += currentPhraseText[opt.currentCharacterIndex];
opt.currentCharacterIndex++;
} else {
opt.currentPhrase = opt.currentPhrase.slice(0, -1);
opt.currentCharacterIndex--;
}
text.value = opt.currentPhrase;
if (opt.currentCharacterIndex === currentPhraseText.length) {
opt.isDeleting = true;
}
if (opt.currentCharacterIndex === 0) {
opt.currentPhrase = "";
opt.isDeleting = false;
opt.currentPhraseIndex++;
if (opt.currentPhraseIndex === opt.phrases?.length) {
opt.currentPhraseIndex = 0;
}
}
const spedUp = Math.random() * (80 - 50) + 50;
const normalSpeed = Math.random() * (300 - 200) + 200;
const time = opt.isDeleting ? spedUp : normalSpeed;
setTimeout(loop, time);
}
onMounted(() => {
loop()
})
return {
text
}
}
})
app.mount('#demo')
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<h2>{{ text }}</h2>
</div>
I found the way to make it work.
The following code is updated and works using Vue.js 3.
<script setup>
import { ref } from "vue";
const phrases = [
"I am John Doe.",
"I am student.",
"I am developer.",
];
const currentPhraseIndex = ref(0);
const currentCharacterIndex = ref(0);
const currentPhrase = ref("");
const isDeleting = ref(false);
function loop() {
const currentPhraseText = phrases[currentPhraseIndex.value];
if (!isDeleting.value) {
currentPhrase.value += currentPhraseText[currentCharacterIndex.value];
currentCharacterIndex.value++;
} else {
currentPhrase.value = currentPhrase.value.slice(0, -1);
currentCharacterIndex.value--;
}
if (currentCharacterIndex.value === currentPhraseText.length) {
isDeleting.value = true;
}
if (currentCharacterIndex.value === 0) {
currentPhrase.value = "";
isDeleting.value = false;
currentPhraseIndex.value++;
if (currentPhraseIndex.value === phrases.length) {
currentPhraseIndex.value = 0;
}
}
const spedUp = Math.random() * (80 - 50) + 50;
const normalSpeed = Math.random() * (300 - 200) + 200;
const time = isDeleting.value ? spedUp : normalSpeed;
setTimeout(loop, time);
}
loop();
</script>
<template>
<div>
<h1 id="title">{{ currentPhrase }}</h1>
</div>
</template>
You have to add this line
if (opt.currentCharacterIndex === currentPhraseText.length) {
opt.isDeleting = true;
opt.currentPhraseIndex = 0; // <===== you have to add this line
}
I created a slider-puzzle game and recently decided to reafctor the code using OOP. Besides I added a new functionality, so a player can change the size of the game board (3X3, 4X4, 5X5 etc.) by pressing a sertain button. But somehow a shuffle function stopped working. If you have any ideas of what I might do wrong, I will be very grateful if you let me know.
"use strict";
///////////////////////////////////////////////////////////////////////////
// Elements
const body = document.body;
const root = document.querySelector(".root");
const buttons = document.createElement("div");
const table = document.querySelector(".table");
const timeAndMoves = document.createElement("div");
const cubeSize = 125;
let cubes = [];
let gameRoundResults = {round: [], moves: [], time: []};
let countMoves = 0;
let gameRound = 0;
let cube, timer, left, top, tr, td;
let rowColumnLength = [3, 4, 5, 6, 7, 8]; // the default size of the game board - 4 columns and 4 rows
let boardSize = [9, 16, 15, 36, 49, 64];
let cubesArray = [];
let blankSpace = document.querySelector(".blank_space");
blankSpace = {
// blank space coords
};
// calculating coordinates of each cube and setting style
function getCoords(a, b) {
return Math.abs(a - b);
}
function setElementStyle(a, b) {
return `${a * b}px`;
}
// changing position of cubes
function changePosition(i) {
let cube = cubes[i];
const blankSpaceLeft = blankSpace.left;
const blankSpaceTop = blankSpace.top;
let coordsLeft = getCoords(blankSpace.left, cube.left);
let coordsTop = getCoords(blankSpace.top, cube.top);
if (
(blankSpaceLeft === cube.left && blankSpaceTop === cube.top) ||
coordsLeft + coordsTop > 1
) {
return;
} else {
countMoves++;
calcMoves.textContent = `Moves: ${countMoves}`;
}
cube.element.style.left = setElementStyle(blankSpace.left, cubeSize);
cube.element.style.top = setElementStyle(blankSpace.top, cubeSize);
blankSpace.top = cube.top;
blankSpace.left = cube.left;
cube.top = blankSpaceTop;
cube.left = blankSpaceLeft;
const winCondition = cubes.every(
(cube) => cube.value - 1 === cube.top * rowColumnLength + cube.left
);
if (winCondition) {
winAnnouncement();
clearInterval(timer);
gameRound++;
gameRoundResults.round.push(gameRound);
gameRoundResults.moves.push(countMoves);
gameRoundResults.time.push(setTimer.textContent);
}
}
// resetting the game, so after clicking on a shuffle button the original game board won't overlay newly shuffled cubes
const resetGame = () => {
clearInterval(timer);
setTimer.textContent = `Time: 00:00`;
countMoves = 0;
calcMoves.textContent = `Moves: ${countMoves}`;
[...root.children].forEach((el) => {
root.removeChild(el); //removing elements from the board
});
[...table.children].forEach((el) => {
table.removeChild(el); //removing elements from the board
});
cubes.splice(0); // deleting all elements starting from index 0
blankSpace.top = 0; // resetting coordinates
blankSpace.left = 0;
};
// creating a timer
const setTimer = document.createElement("div");
setTimer.classList.add("timer");
setTimer.textContent = `Time: 00:00`;
body.append(setTimer);
function startTimer() {
let sec = 0;
let min = 0;
timer = setInterval(() => {
setTimer.textContent =
"Time: " + `${min}`.padStart(2, 0) + ":" + `${sec}`.padStart(2, 0);
sec++;
if (sec >= 60) {
sec = 0;
min++;
}
}, 1000);
}
// creating moves counter
const calcMoves = document.createElement("div");
calcMoves.classList.add("count_moves");
calcMoves.textContent = `Moves: ${countMoves}`;
body.append(calcMoves);
////////////////////////////////////////////////////////////////////////////////////
// Creating buttons using OOP and adding event listeners to each of them
class Button {
constructor(name, attribute, classList, textContent, value) {
this.name = document.createElement("button");
this.name.setAttribute("id", attribute);
this.name.classList.add(classList);
this.textContent = this.name.textContent = textContent;
buttons.append(this.name);
}
//Methods that will be added to .prototype property
addEventShuffle() {
this.name.addEventListener("click", function () {
shuffle(cubesArray);
});
}
addEventStop() {
this.name.addEventListener("click", function () {
clearInterval(timer);
});
}
addEventResult() {
this.name.addEventListener("click", function () {
clearInterval(timer);
tableCreate();
showResults();
});
}
clickOnlyOnce() {
this.name.addEventListener("click", function () {
this.disabled = "disabled";
});
}
clickOnlyOnceDisabled() {
this.name.removeAttribute("disabled");
}
renderCubesNumber(number){
this.name.addEventListener("click", function () {
cubesArray.push(...Array(boardSize[number]).keys());
setPosition(boardSize = boardSize[number], rowColumnLength = rowColumnLength[number]);
console.log(cubesArray);
});
}
}
const shuffleButton = new Button(
"shuffleButton",
"button",
"shuffleButton",
"SHUFFLE"
);
const stopButton = new Button("stopButton", "button", "stopButton", "STOP");
const saveButton = new Button("saveButton", "button", "saveButton", "SAVE");
const resultsButton = new Button( "resultsButton", "button", "resultsButton", "RESULT");
shuffleButton.addEventShuffle();
stopButton.addEventStop();
resultsButton.addEventResult();
resultsButton.clickOnlyOnce();
// Creating size buttons
for(let i = 3; i <= 8; i++){
new Button("rowsCols", "button", "row_col_length", `${i}X${i}`).renderCubesNumber(i - 3);
}
// implementing shuffle functionality
function shuffle(cubesArray) {
for (let i = cubesArray.length - 1; i > 0; i--) {
resetGame(); // deleting original game board
setPosition(cubesArray); // creating a new shuffled game board
startTimer();
let j = Math.floor(Math.random() * (i + 1));
[cubesArray[i], cubesArray[j]] = [cubesArray[j], cubesArray[i]];
// [cubesArray[i], cubesArray[newPos]] = [cubesArray[newPos], cubesArray[i]]; //swapping original and randomized coordinates
console.log(
([cubesArray[i], cubesArray[j]] = [cubesArray[j], cubesArray[i]])
);
}
}
////////////////////////////////////////////////////////////////////////////////////
// Creating banners using OOP
class Banner {
constructor(name, classList, textContent, bannerMessage, bannerMessageClass) {
this.name = document.createElement("div");
this.name.classList.add(classList);
this.bannerMessage = document.createElement("h2");
this.bannerMessage.classList.add(bannerMessageClass);
this.bannerMessage.textContent = textContent;
root.append(this.bannerMessage);
root.append(this.name);
}
removeBanner() {
this.name.addEventListener("click", () => {
resultBanner.classList.remove("result_banner");
resultsButton.clickOnlyOnceDisabled();
});
}
}
// const startBanner = new Banner("startBanner","start_banner", `Slider puzzle challenge Click on 'Shuffle' to start the game!`, "bannerMessage","banner_message"
// );
// create pop-up if the user wins
function winAnnouncement() {
const winMessage = new Banner(
"winAnnounce",
"win_announce",
`Congrats! You solved the puzzle! Moves: ${countMoves} | ${setTimer.textContent}`,
"winMessage",
"win_message"
);
}
// Creating a result banner
function showResults() {
const resultBanner = document.createElement("div");
resultBanner.classList.add("result_banner");
const closeBanner = document.createElement("div");
closeBanner.classList.add("close_banner");
root.append(resultBanner);
table.append(closeBanner);
tableCreate();
const resultMessage = document.createElement("h2");
resultMessage.classList.add("banner_message");
resultBanner.append(resultMessage);
closeBanner.addEventListener("click", () => {
resultBanner.classList.remove("result_banner");
resultsButton.clickOnlyOnceDisabled();
[...table.children].forEach((el) => {
table.removeChild(el); //removing elements from the board
});
});
}
function tableCreate() {
const tbl = document.createElement("table");
tbl.classList.add("score_table");
for (let i = 0; i < 11; i++) {
tr = tbl.insertRow();
td = tr.insertCell();
if (gameRound === i + 1) {
td.appendChild(
document.createTextNode(
`Round: ${i + 1} | Moves: ${gameRoundResults.moves[i]} | ${gameRoundResults.time[i]}`
)
);
} else {
td.appendChild(
document.createTextNode(`Round: ${i + 1} | Moves: 0 | Time: 00:00`)
);
}
td.style.border = "1px solid black";
}
table.appendChild(tbl);
}
buttons.classList.add("button");
body.append(buttons);
// creating cubes
function setPosition() {
for (let i = 0; i < boardSize; i++) {
cube = document.createElement("div");
cube.classList.add("cube");
const value = i + 1; // adding +1 to the cubes' values to start the order from 1 (123...) instead of 0 (0123...)
cube.textContent = value;
left = i % rowColumnLength; // setting horizontal position
cube.style.left = setElementStyle(cubeSize, left);
top = (i - left) / rowColumnLength; // setting vertical position
cube.style.top = setElementStyle(cubeSize, top);
root.append(cube);
if (value === boardSize) {
// separating basic cubes with a blank space which is going to be the 16th cube
cube.classList.add("blank_space"); // adding a class to the last cube, so it'll be possible to hide it using css
blankSpace.top = top;
blankSpace.left = left;
blankSpace.value = value;
blankSpace.element = cube;
cubes.push(blankSpace);
} else {
cubes.push({
top: top,
left: left,
value: value,
element: cube,
});
}
cube.addEventListener("click", () => {
changePosition(i);
});
}
}
setPosition();
To solve the problem I've tried to refactor the shuffle function, but nothing works. In the screenshot you can see the results of console.logs
The problem with the code below is that the Counters do not stop at the same time. Is there any way to adjust the duration of the Counters? As far as I know, by using animate function JQuery it is possible to adjust the duration, but I wonder how to combine that with Intersection Observer in order to run animated numbers just as they become visible.
const counterElements = document.querySelectorAll(".count");
// Counters
function counter(target, start, stop) {
target.innerText = 0.1;
const counterInterval = setInterval(() => {
start += 0.1;
const valueConverted = (Math.round(start * 100) / 100).toFixed(1);
target.innerText = valueConverted;
if (valueConverted == stop) {
clearInterval(counterInterval);
}
}, 30);
}
function obCallBack(entries) {
entries.forEach((entry) => {
const { target } = entry;
const stopValue = target.innerText;
const startValue = 0;
if (!entry.isIntersecting) return;
counter(target, startValue, stopValue);
counterObserver.unobserve(target);
});
}
const counterObserver = new IntersectionObserver(obCallBack, { threshold: 1 });
counterElements.forEach((counterElem) => counterObserver.observe(counterElem));
.emptyspace{
height:400px;
}
<div class="emptyspace"></div>
<p class="count">5.2</p>
<p class="count">50.9</p>
</div>
You should use a ratio rather than a fixed number.
const speed = 100;
const inc = Number(stop / speed);
const counterElements = document.querySelectorAll(".count");
const speed = 100; // the lower the slower
// Counters
function counter(target, start, stop) {
target.innerText = 0.1;
const counterInterval = setInterval(() => {
const inc = Number(stop / speed);
start += inc;
const valueConverted = (Math.round(start * 100) / 100).toFixed(1);
target.innerText = valueConverted;
if (valueConverted == stop) {
clearInterval(counterInterval);
}
}, 30);
}
function obCallBack(entries) {
entries.forEach((entry) => {
const { target } = entry;
const stopValue = target.innerText;
const startValue = 0;
if (!entry.isIntersecting) return;
counter(target, startValue, stopValue);
counterObserver.unobserve(target);
});
}
const counterObserver = new IntersectionObserver(obCallBack, { threshold: 1 });
counterElements.forEach((counterElem) => counterObserver.observe(counterElem));
.emptyspace{
height:400px;
}
<div class="emptyspace"></div>
<p class="count">5.2</p>
<p class="count">50.9</p>
</div>
Hello evreyone I want to know how to continue setsetinterval function after clearing it starting from the last value
for example I created counter and i cleared it at the value "10" how to make it start counting again starting from the last value "10"
this is my code
let
div = document.getElementById("test");
button = document.getElementById("btn");
let time =_ =>
this.time = 0
counter = setInterval(function()
{
this.time = this.time+1;
div.innerHTML=this.time + "s"
}
,1000);
time()
button.onclick = function()
{
clearInterval(counter)
}`
Try this
.js
let time = 0;
let counter = null;
let div = document.getElementById("test");
let button = document.getElementById("btn");
const startCounting = () => {
counter = setInterval(() => {
time += 1;
div.innerHTML = time + "s";
}, 100);
};
const startOrStop = () => {
if (counter) {
clearInterval(counter);
counter = null;
} else {
startCounting();
}
};
button.onclick = function () {
startOrStop();
};
.html
<div id="test"></div>
<button id="btn">Start/Stop</button>
Here is the sandbox link - https://codesandbox.io/s/floral-thunder-q4vmc?file=/index.html:115-182
This code is weird but you can just again do it:
counter = setInterval(function()
{
this.time = this.time+1;
div.innerHTML=this.time + "s"
}
,1000);
And interval should work again and I would name this function:
counter = setInterval(nameOfFunction, 1000);
Set a variable to control whether setInterval is going:
var intervalgoing = true;
setInterval(()=> {
if (intervalgoing){
// Do stuff here
}
})
// Set "intervalgoing" to false to stop it.
To continue the countdown, simply call setInterval again:
let t = document.querySelector('.time');
let b = document.querySelector('.control');
let state = 'paused';
let count = 0;
let interval = null;
b.addEventListener('click', () => {
if (state === 'paused') {
state = 'running';
b.textContent = 'Pause';
interval = setInterval(() => {
count++;
t.textContent = `${count}s`;
}, 1000);
} else {
state = 'paused';
b.textContent = 'Resume';
clearInterval(interval);
}
});
<div class="time">0</div>
<button class="control">Start</button>
The above approach does not track partial seconds. For example, clicking pause/resume repetitively, very quickly, will prevent the display from ever showing that a single second has passed. We could get a finer sense of time elapsed by storing a millisecond delta:
let t = document.querySelector('.time');
let b = document.querySelector('.control');
let state = 'paused';
let totalMs = 0;
let playMs = null;
let interval = null;
b.addEventListener('click', () => {
if (state === 'paused') {
state = 'running';
b.textContent = 'Pause';
playMs = +new Date(); // int defining start time
let updateFn = () => {
let ms = totalMs + (new Date() - playMs);
t.textContent = `${Math.floor(ms * 0.001)}s`;
};
interval = setInterval(updateFn, 500);
updateFn();
} else {
state = 'paused';
b.textContent = 'Resume';
totalMs += (new Date() - playMs);
clearInterval(interval);
}
});
<div class="time">0s</div>
<button class="control">Start</button>
I have 2 divs witch I need to animate:
<div class="d"></div>
<div class="p"></div>
Width of first div should become 70% and width of second div should become 30%. But when I'am trying to animate one div after another, calling at first function for 70% and then function for 30%, width of both of them become 30%.
Javascript code:
Anim({
target: document.getElementsByClassName('d')[0],
drawFunc: (progress, element) => {
element.style.width = (progress * 70) + '%';
}
});
Anim({
target: document.getElementsByClassName('p')[0],
drawFunc: (progress, element) => {
element.style.width = (progress * 30) + '%';
}
});
I don't understand why this is happening and how to make both functions work correctly.
Code snippet if needed:
(() => {
"use strict";
const init = (params) => {
const start = performance.now();
const element = params.target || null;
requestAnimationFrame(function animate(time) {
let timeFraction = (time - start) / params.duration;
if (timeFraction > 1) {
timeFraction = 1;
}
const progress = params.timingFunc(timeFraction, params.timingArg);
params.drawFunc(progress, element);
if (timeFraction < 1) {
requestAnimationFrame(animate);
}
if (params.callback) {
if (timeFraction >= 1) {
params.callback();
}
}
});
};
const timingFunctions = {
linear: (timeFraction) => {
return timeFraction;
}
};
const paces = {
easeIn: (func) => {
return timingFunctions[func];
}
};
const defaultParams = {
duration: 1000,
timingFunc: paces.easeIn('linear'),
timingArg: null,
delay: null,
callback: null
};
const makeParams = (def, add) => {
let params = def;
if (add) {
for (let i in add) {
if (Object.prototype.hasOwnProperty.call(add, i)) {
params[i] = add[i];
}
}
}
return params;
};
function Anim(paramArgs) {
const params = makeParams(defaultParams, paramArgs);
if ('timingFunc' in paramArgs) {
params.timingFunc = (typeof paramArgs.timingFunc === 'function') ? paramArgs.timingFunc : paces[paramArgs.timingFunc.pace](paramArgs.timingFunc.func);
}
if (!params.delay) {
init(params);
} else {
setTimeout(() => {
init(params);
}, params.delay);
}
}
window.Anim = Anim;
})();
Anim({
target: document.getElementsByClassName('d')[0],
drawFunc: (progress, element) => {
element.style.width = (progress * 70) + '%';
}
});
Anim({
target: document.getElementsByClassName('p')[0],
drawFunc: (progress, element) => {
element.style.width = (progress * 30) + '%';
}
});
.d, .p {
background-color: red;
height: 50px;
width: 0;
margin-top: 10px;
}
<div class="d"></div>
<div class="p"></div>
The problem is that both Anim calls have the same params object. Both params objects have the same exact callback drawFunc.
Why? Because in makeParams you are doing this:
let params = def;
Then you assign to params which in turn alters the original defaultParams (aliased here as def). When the second function calls Anim, the callback drawFunc of this second call gets assigned to the defaultParams object. Since all params objects are basically a reference to defaultParams, they get altered too, and the callback of the last call to Anim gets assigned to all of them.
To fix this, just clone def using Object.assign:
let params = Object.assign({}, def);
Side note: The target property is also altered in the params object, but before it changes, it gets assigned to a new variable inside init:
const element = params.target || null;
Thus, even though it changes in the params object, you don't really notice because all subsequent code uses the variable element instead of params.target.
Working code:
(() => {
"use strict";
const init = (params) => {
const start = performance.now();
const element = params.target || null;
requestAnimationFrame(function animate(time) {
let timeFraction = (time - start) / params.duration;
if (timeFraction > 1) {
timeFraction = 1;
}
const progress = params.timingFunc(timeFraction, params.timingArg);
params.drawFunc(progress, element);
if (timeFraction < 1) {
requestAnimationFrame(animate);
}
if (params.callback) {
if (timeFraction >= 1) {
params.callback();
}
}
});
};
const timingFunctions = {
linear: (timeFraction) => {
return timeFraction;
}
};
const paces = {
easeIn: (func) => {
return timingFunctions[func];
}
};
const defaultParams = {
duration: 1000,
timingFunc: paces.easeIn('linear'),
timingArg: null,
delay: null,
callback: null
};
const makeParams = (def, add) => {
let params = Object.assign({}, def);
if (add) {
for (let i in add) {
if (Object.prototype.hasOwnProperty.call(add, i)) {
params[i] = add[i];
}
}
}
return params;
};
function Anim(paramArgs) {
const params = makeParams(defaultParams, paramArgs);
if ('timingFunc' in paramArgs) {
params.timingFunc = (typeof paramArgs.timingFunc === 'function') ? paramArgs.timingFunc : paces[paramArgs.timingFunc.pace](paramArgs.timingFunc.func);
}
if (!params.delay) {
init(params);
} else {
setTimeout(() => {
init(params);
}, params.delay);
}
}
window.Anim = Anim;
})();
Anim({
target: document.getElementsByClassName('d')[0],
drawFunc: (progress, element) => {
element.style.width = (progress * 70) + '%';
}
});
Anim({
target: document.getElementsByClassName('p')[0],
drawFunc: (progress, element) => {
element.style.width = (progress * 30) + '%';
}
});
.d, .p {
background-color: red;
height: 50px;
width: 0;
margin-top: 10px;
}
<div class="d"></div>
<div class="p"></div>
Related issue: How do I correctly clone a JavaScript object?