Math.random reproducing value twice - javascript

Having a bit of problem with something surely most of you will find trivial, but still would appreciate your help. It's our favorite Rock Papaer Scissors game:
var user = document.querySelector(".userchoice");
var computer = document.querySelector(".computerchoice");
var startBtn = document.querySelector(".startBtn");
var result = document.querySelector(".result");
var winStates = ["Rock", "Paper", "Scissors"];
startBtn.addEventListener("click", playGame);
function playGame() {
startBtn.disabled = true;
user.classList.add("count-in");
computer.classList.add("count-in");
}
document.addEventListener("animationend", function () {
user.classList.remove("count-in");
computer.classList.remove("count-in");
startBtn.disabled = false;
var randomU = winStates[Math.floor(Math.random() * 3)];
var randomC = winStates[Math.floor(Math.random() * 3)];
computer.className = "computerchoice " + randomC;
user.className = "userchoice " + randomU;
});
.container {
width: 650px;
margin: 0 auto;
text-align: center;
padding: 25px;
}
.player {
width: 150px;
height: 150px;
margin: 100px 50px;
display: inline-block;
}
.player p {
font-weight: 700;
}
.player div {
width: 100%;
height: 100%;
}
.Rock {
background-image: url(https://placehold.it/100x100?text=Rock);
}
.Paper {
background-image: url(https://placehold.it/100x100?text=Paper);
}
.Scissors {
background-image: url(https://placehold.it/100x100?text=Scissors);
}
.Rock,
.Paper,
.Scissors {
background-size: contain;
background-repeat: no-repeat;
}
.startBtn {
border: none;
padding: 10px 100px;
}
#keyframes count-in {
0% {
transform: translateY(0);
}
60% {
transform: translateY(-100px);
}
100% {
transform: translateY(0);
}
}
.count-in {
animation: 0.7s count-in 3;
}
<div class="container">
<h1>Rock, Paper, Scissors</h1>
<div class="player">
<div class="userchoice Rock"></div>
<p>Player 1</p>
</div>
<div class="player">
<div class="computerchoice Rock"></div>
<p>Computer</p>
</div>
<div class="result"></div>
<button class="startBtn">Play</button>
</div>
<script src="app.js"></script>
The problem gets when Math.random is executed, it seems like it is being triggered two times and getting two values out of the winStates array. Naturaly it should pick one value and concat it with the css class already defined

The count-in class is added to two elements so the animationend event listener will fire twice.
Since the animations run concurrently, and therefore end at the same moment, you should be able to get away with binding the event to a single element, userchoice or computerchoice by using...
document.querySelector('.userchoice').addEventListener("animationend", ...
var user = document.querySelector(".userchoice");
var computer = document.querySelector(".computerchoice");
var startBtn = document.querySelector(".startBtn");
var result = document.querySelector(".result");
var winStates = ["Rock", "Paper", "Scissors"];
startBtn.addEventListener("click", playGame);
function playGame() {
startBtn.disabled = true;
user.classList.add("count-in");
computer.classList.add("count-in");
}
document.querySelector('.userchoice').addEventListener("animationend", function () {
console.log('animationend event');
user.classList.remove("count-in");
computer.classList.remove("count-in");
startBtn.disabled = false;
var randomU = winStates[Math.floor(Math.random() * 3)];
var randomC = winStates[Math.floor(Math.random() * 3)];
computer.className = "computerchoice " + randomC;
user.className = "userchoice " + randomU;
});
.container {
width: 650px;
margin: 0 auto;
text-align: center;
padding: 25px;
}
.player {
width: 150px;
height: 150px;
margin: 100px 50px;
display: inline-block;
}
.player p {
font-weight: 700;
}
.player div {
width: 100%;
height: 100%;
}
.Rock {
background-image: url(https://placehold.it/100x100?text=Rock);
}
.Paper {
background-image: url(https://placehold.it/100x100?text=Paper);
}
.Scissors {
background-image: url(https://placehold.it/100x100?text=Scissors);
}
.Rock,
.Paper,
.Scissors {
background-size: contain;
background-repeat: no-repeat;
}
.startBtn {
border: none;
padding: 10px 100px;
}
#keyframes count-in {
0% {
transform: translateY(0);
}
60% {
transform: translateY(-100px);
}
100% {
transform: translateY(0);
}
}
.count-in {
animation: 0.7s count-in 3;
}
<div class="container">
<h1>Rock, Paper, Scissors</h1>
<div class="player">
<div class="userchoice Rock"></div>
<p>Player 1</p>
</div>
<div class="player">
<div class="computerchoice Rock"></div>
<p>Computer</p>
</div>
<div class="result"></div>
<button class="startBtn">Play</button>
</div>
<script src="app.js"></script>

Related

Why the onAnimationEnd does not trigger when switching the focus to other tab of the browser?

I am trying to build a split-flap.
Here is my code:
let baseDiv, lowerDiv, middleDiv, upperDiv;
let intervalId;
document.addEventListener("DOMContentLoaded", () => {
baseDiv = document.getElementById("base");
lowerDiv = document.getElementById("lower");
middleDiv = document.getElementById("middle");
upperDiv = document.getElementById("upper");
});
let backward = () => {
middleDiv.innerHTML = baseDiv.innerHTML;
lowerDiv.classList.add("rotate0to90");
middleDiv.className = "upperHalfCard-after transform0to_90 zIndex4";
}
let forward = () => {
middleDiv.innerHTML = baseDiv.innerHTML;
upperDiv.classList.add("rotate0to_90");
middleDiv.className = "lowerHalfCard-after transform0to90 zIndex4";
}
let handler = obj => {
console.log(obj.id);
switch (obj.id) {
case "lower":
lowerDiv.classList.replace("zIndex4", "zIndex2");
middleDiv.classList.add("rotate_90to0");
break;
case "middle":
upperDiv.innerHTML = baseDiv.innerHTML;
lowerDiv.innerHTML = baseDiv.innerHTML;
middleDiv.className = "hide";
upperDiv.className = "upperHalfCard-after zIndex4";
lowerDiv.className = "lowerHalfCard-after zIndex2";
break;
case "upper":
middleDiv.classList.add("rotate90to0");
upperDiv.classList.replace("zIndex4", "zIndex2");
break;
default:
break;
}
}
let start = () => {
intervalId = setInterval(() => {
console.log("Kicked by interval");
forward();
}, 3000);
}
let stop = () => {
clearInterval(intervalId);
}
.fullCard,
.lowerHalfCard,
.upperHalfCard,
.fullCard-after,
.lowerHalfCard-after,
.upperHalfCard-after {
background-color: inherit;
border-radius: 10px;
height: 100%;
width: 100%;
position: absolute;
align-items: inherit;
display: inherit;
justify-content: inherit;
}
.fullCard-after::after,
.upperHalfCard-after::after {
content: "";
display: block;
position: absolute;
height: 4px;
background-color: inherit;
width: 100%;
top: calc(50% - 2px);
}
.lowerHalfCard-after::after {
content: "";
display: block;
position: absolute;
height: 4px;
background-color: inherit;
width: 100%;
top: calc(50% - 2px);
}
.lowerHalfCard,
.lowerHalfCard-after {
clip-path: polygon(0% 50%, 100% 50%, 100% 100%, 0% 100%);
}
.upperHalfCard,
.upperHalfCard-after {
clip-path: polygon(0% 0%, 100% 0%, 100% 50%, 0% 50%);
}
.splitFlap {
background-color: black;
box-sizing: border-box;
border-radius: 10px;
color: white;
font-weight: bold;
font-family: arial;
font-size: 5.5em;
width: 100px;
height: 150px;
position: relative;
align-items: center;
display: flex;
justify-content: center;
transform-style: preserve-3d;
}
.rotate0to90 {
animation-name: r0to90;
}
.rotate90to0 {
animation-name: r90to0;
}
.rotate0to_90 {
animation-name: r0to_90;
}
.rotate_90to0 {
animation-name: r_90to0;
}
.rotate0to90,
.rotate90to0,
.rotate0to_90,
.rotate_90to0 {
animation-duration: 0.3s;
animation-fill-mode: forwards;
}
#keyframes r0to90 {
from {
transform: rotateX(0deg);
}
to {
transform: rotateX(90deg);
}
}
#keyframes r90to0 {
from {
transform: rotateX(90deg);
}
to {
transform: rotateX(0deg);
}
}
#keyframes r0to_90 {
from {
transform: rotateX(0deg);
}
to {
transform: rotateX(-90deg);
}
}
#keyframes r_90to0 {
from {
transform: rotateX(-90deg);
}
to {
transform: rotateX(0deg);
}
}
.transform0to_90 {
transform: rotateX(-90deg);
}
.transform0to90 {
transform: rotateX(90deg);
}
.hide {
display: none
}
.zIndex2 {
z-index: 2;
}
.zIndex4 {
z-index: 4;
}
<div class="splitFlap">
<div id="base" class="fullCard-after zIndex2">
2
</div>
<div class="upperHalfCard-after zIndex4" id="upper" onAnimationEnd="handler(this)">
1
</div>
<div id="middle" class="hide" onAnimationEnd="handler(this)">
</div>
<div class="lowerHalfCard-after zIndex2" id="lower" onAnimationEnd="handler(this)">
1
</div>
</div>
<p>
<button onClick="start()">
Start
</button>
<button onClick="stop()">
Stop
</button>
</p>
The code works fine where the tab is on focus.
And you can see the onAnimationEnd event handler and interval handler work properly(i.e. 1 interval event trigger 2 onAnimationEnd event.).
Unfortunately, when switching the browser focus to another tab for about 1 min, the onAnimationEnd event handler seems to be not stable(i.e. sometimes only an interval event is triggered, and no onAnimationEnd event is triggered, sometimes the onAnimationEnd event handler can be resume).
What's going on? how can I fix it?

memory js game pictures won't close

I found one game on js, and I want to add it to myself by redoing it a bit
https://jsfiddle.net/a7Lx1c98/
So, I want to replace emoji with pictures here, I do this
const emojis = ['https://i.imgur.com/GLS9S5f.jpg', 'https://i.imgur.com/IN9C2qz.jpg', 'https://i.imgur.com/Ke2ubzv.jpg', 'https://i.imgur.com/PbvJDyR.jpg', 'https://i.imgur.com/L3ysai2.jpg',
'https://i.imgur.com/1NxzhTV.jpg', 'https://i.imgur.com/aksV9O3.jpg', 'https://i.imgur.com/gYsZdE4.jpg', 'https://i.imgur.com/LXo6iW3.jpg', 'https://i.imgur.com/wYrEwNR.jpg']
const picks = pickRandom(emojis, (dimensions * dimensions) / 2)
const items = shuffle([...picks, ...picks])
const cards = `
<div class="board" style="grid-template-columns: repeat(${dimensions}, auto)">
${items.map(item => `
<div class="card">
<div class="card-front"></div>
<div class="card-back"><img src="${item}"></div>
</div>
`).join('')}
</div>
`
As a result, the pictures appear, but the game itself does not work, when you select two pictures and they are different, they do not close, but you can choose everything in a row
What could be wrong?
const selectors = {
boardContainer: document.querySelector('.board-container'),
board: document.querySelector('.board'),
moves: document.querySelector('.moves'),
timer: document.querySelector('.timer'),
start: document.querySelector('button'),
win: document.querySelector('.win')
}
const state = {
gameStarted: false,
flippedCards: 0,
totalFlips: 0,
totalTime: 0,
loop: null
}
const shuffle = array => {
const clonedArray = [...array]
for (let index = clonedArray.length - 1; index > 0; index--) {
const randomIndex = Math.floor(Math.random() * (index + 1))
const original = clonedArray[index]
clonedArray[index] = clonedArray[randomIndex]
clonedArray[randomIndex] = original
}
return clonedArray
}
const pickRandom = (array, items) => {
const clonedArray = [...array]
const randomPicks = []
for (let index = 0; index < items; index++) {
const randomIndex = Math.floor(Math.random() * clonedArray.length)
randomPicks.push(clonedArray[randomIndex])
clonedArray.splice(randomIndex, 1)
}
return randomPicks
}
const generateGame = () => {
const dimensions = selectors.board.getAttribute('data-dimension')
if (dimensions % 2 !== 0) {
throw new Error("The dimension of the board must be an even number.")
}
const emojis = ['https://i.imgur.com/GLS9S5f.jpg', 'https://i.imgur.com/IN9C2qz.jpg', 'https://i.imgur.com/Ke2ubzv.jpg', 'https://i.imgur.com/PbvJDyR.jpg', 'https://i.imgur.com/L3ysai2.jpg',
'https://i.imgur.com/1NxzhTV.jpg', 'https://i.imgur.com/aksV9O3.jpg', 'https://i.imgur.com/gYsZdE4.jpg', 'https://i.imgur.com/LXo6iW3.jpg', 'https://i.imgur.com/wYrEwNR.jpg']
const picks = pickRandom(emojis, (dimensions * dimensions) / 2)
const items = shuffle([...picks, ...picks])
const cards = `
<div class="board" style="grid-template-columns: repeat(${dimensions}, auto)">
${items.map(item => `
<div class="card">
<div class="card-front"></div>
<div class="card-back"><img src="${item}"></div>
</div>
`).join('')}
</div>
`
const parser = new DOMParser().parseFromString(cards, 'text/html')
selectors.board.replaceWith(parser.querySelector('.board'))
}
const startGame = () => {
state.gameStarted = true
selectors.start.classList.add('disabled')
state.loop = setInterval(() => {
state.totalTime++
selectors.moves.innerText = `${state.totalFlips} moves`
selectors.timer.innerText = `time: ${state.totalTime} sec`
}, 1000)
}
const flipBackCards = () => {
document.querySelectorAll('.card:not(.matched)').forEach(card => {
card.classList.remove('flipped')
})
state.flippedCards = 0
}
const flipCard = card => {
state.flippedCards++
state.totalFlips++
if (!state.gameStarted) {
startGame()
}
if (state.flippedCards <= 2) {
card.classList.add('flipped')
}
if (state.flippedCards === 2) {
const flippedCards = document.querySelectorAll('.flipped:not(.matched)')
if (flippedCards[0].innerText === flippedCards[1].innerText) {
flippedCards[0].classList.add('matched')
flippedCards[1].classList.add('matched')
}
setTimeout(() => {
flipBackCards()
}, 1000)
}
// If there are no more cards that we can flip, we won the game
if (!document.querySelectorAll('.card:not(.flipped)').length) {
setTimeout(() => {
selectors.boardContainer.classList.add('flipped')
selectors.win.innerHTML = `
<span class="win-text">
You won!<br />
with <span class="highlight">${state.totalFlips}</span> moves<br />
under <span class="highlight">${state.totalTime}</span> seconds
</span>
`
clearInterval(state.loop)
}, 1000)
}
}
const attachEventListeners = () => {
document.addEventListener('click', event => {
const eventTarget = event.target
const eventParent = eventTarget.parentElement
if (eventTarget.className.includes('card') && !eventParent.className.includes('flipped')) {
flipCard(eventParent)
} else if (eventTarget.nodeName === 'BUTTON' && !eventTarget.className.includes('disabled')) {
startGame()
}
})
}
generateGame()
attachEventListeners()
html {
width: 100%;
height: 100%;
background: linear-gradient(325deg, #6f00fc 0%,#fc7900 50%,#fcc700 100%);
font-family: Fredoka;
}
.game {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.controls {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
button {
background: #282A3A;
color: #FFF;
border-radius: 5px;
padding: 10px 20px;
border: 0;
cursor: pointer;
font-family: Fredoka;
font-size: 18pt;
}
.disabled {
color: #757575;
}
.stats {
color: #FFF;
font-size: 14pt;
}
.board-container {
position: relative;
}
.board,
.win {
border-radius: 5px;
box-shadow: 0 25px 50px rgb(33 33 33 / 25%);
background: linear-gradient(135deg, #6f00fc 0%,#fc7900 50%,#fcc700 100%);
transition: transform .6s cubic-bezier(0.4, 0.0, 0.2, 1);
backface-visibility: hidden;
}
.board {
padding: 20px;
display: grid;
grid-template-columns: repeat(4, auto);
grid-gap: 20px;
}
.board-container.flipped .board {
transform: rotateY(180deg) rotateZ(50deg);
}
.board-container.flipped .win {
transform: rotateY(0) rotateZ(0);
}
.card {
position: relative;
width: 100px;
height: 100px;
cursor: pointer;
}
.card-front,
.card-back {
position: absolute;
border-radius: 5px;
width: 100%;
height: 100%;
background: #282A3A;
transition: transform .6s cubic-bezier(0.4, 0.0, 0.2, 1);
backface-visibility: hidden;
}
.card-back {
transform: rotateY(180deg) rotateZ(50deg);
font-size: 28pt;
user-select: none;
text-align: center;
line-height: 100px;
background: #FDF8E6;
}
.card.flipped .card-front {
transform: rotateY(180deg) rotateZ(50deg);
}
.card.flipped .card-back {
transform: rotateY(0) rotateZ(0);
}
.win {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
text-align: center;
background: #FDF8E6;
transform: rotateY(180deg) rotateZ(50deg);
}
.win-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 21pt;
color: #282A3A;
}
.highlight {
color: #6f00fc;
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>🧠 Memory Game in JavaScript</title>
<link rel="stylesheet" href="assets/styles.css" />
<script src="assets/game.js" defer></script>
</head>
<body>
<div class="game">
<div class="controls">
<button>Start</button>
<div class="stats">
<div class="moves">0 moves</div>
<div class="timer">time: 0 sec</div>
</div>
</div>
<div class="board-container">
<div class="board" data-dimension="4"></div>
<div class="win">You won!</div>
</div>
</div>
</body>
</html>
Because every pair is a match:
if (flippedCards[0].innerText === flippedCards[1].innerText)
Your elements have no text, so innerText is always an empty string. So any two cards, regardless of their images, match.
Probably the quickest solution is to compare the HTML instead:
if (flippedCards[0].innerHTML === flippedCards[1].innerHTML)
Assuming the rest of the HTML is always the same, the only different should be the src on the <img> element(s).
As an added exercise, you could also look into being more explicit in that comparison. Perhaps give each element a data-* property and compare those instead of relying on the text or HTML. Or perhaps specifically read the src property and compare those values instead of comparing the entire contents of the element(s).
The issue is comparing the innerText of the two cards. There is no inner text, so you can compare the card back image URLs.
const selected = [...flippedCards].map(card => card.querySelector('.card-back img').src);
if (allEqual(selected)) {
flippedCards[0].classList.add('matched')
flippedCards[1].classList.add('matched')
}
I also added an allEqual function to make sure all the items match:
const allEqual = arr => arr.every(v => v === arr[0]);
Working example
const allEqual = arr => arr.every(v => v === arr[0]);
const selectors = {
boardContainer: document.querySelector('.board-container'),
board: document.querySelector('.board'),
moves: document.querySelector('.moves'),
timer: document.querySelector('.timer'),
start: document.querySelector('button'),
win: document.querySelector('.win')
};
const state = {
gameStarted: false,
flippedCards: 0,
totalFlips: 0,
totalTime: 0,
loop: null
};
const shuffle = array => {
const clonedArray = [...array];
for (let index = clonedArray.length - 1; index > 0; index--) {
const randomIndex = Math.floor(Math.random() * (index + 1));
const original = clonedArray[index];
clonedArray[index] = clonedArray[randomIndex];
clonedArray[randomIndex] = original;
}
return clonedArray;
}
const pickRandom = (array, items) => {
const clonedArray = [...array];
const randomPicks = [];
for (let index = 0; index < items; index++) {
const randomIndex = Math.floor(Math.random() * clonedArray.length);
randomPicks.push(clonedArray[randomIndex]);
clonedArray.splice(randomIndex, 1);
}
return randomPicks;
}
const generateGame = () => {
const dimensions = selectors.board.getAttribute('data-dimension');
if (dimensions % 2 !== 0) {
throw new Error("The dimension of the board must be an even number.");
}
const emojis = [
'https://i.imgur.com/GLS9S5f.jpg',
'https://i.imgur.com/IN9C2qz.jpg',
'https://i.imgur.com/Ke2ubzv.jpg',
'https://i.imgur.com/PbvJDyR.jpg',
'https://i.imgur.com/L3ysai2.jpg',
'https://i.imgur.com/1NxzhTV.jpg',
'https://i.imgur.com/aksV9O3.jpg',
'https://i.imgur.com/gYsZdE4.jpg',
'https://i.imgur.com/LXo6iW3.jpg',
'https://i.imgur.com/wYrEwNR.jpg'
];
const picks = pickRandom(emojis, (dimensions * dimensions) / 2);
const items = shuffle([...picks, ...picks]);
const cards = `
<div class="board" style="grid-template-columns: repeat(${dimensions}, auto)">
${items.map(item => `
<div class="card">
<div class="card-front"></div>
<div class="card-back"><img src="${item}"></div>
</div>
`).join('')}
</div>
`;
const parser = new DOMParser().parseFromString(cards, 'text/html');
selectors.board.replaceWith(parser.querySelector('.board'));
}
const startGame = () => {
state.gameStarted = true
selectors.start.classList.add('disabled');
state.loop = setInterval(() => {
state.totalTime++;
selectors.moves.innerText = `${state.totalFlips} moves`;
selectors.timer.innerText = `time: ${state.totalTime} sec`;
}, 1000);
}
const flipBackCards = () => {
document.querySelectorAll('.card:not(.matched)').forEach(card => {
card.classList.remove('flipped');
})
state.flippedCards = 0;
}
const flipCard = card => {
state.flippedCards++;
state.totalFlips++;
if (!state.gameStarted) {
startGame();
}
if (state.flippedCards <= 2) {
card.classList.add('flipped');
}
if (state.flippedCards === 2) {
const flippedCards = document.querySelectorAll('.flipped:not(.matched)')
const selected = [...flippedCards].map(card => card.querySelector('.card-back img').src);
if (allEqual(selected)) {
flippedCards[0].classList.add('matched')
flippedCards[1].classList.add('matched')
}
setTimeout(() => {
flipBackCards();
}, 1000);
}
// If there are no more cards that we can flip, we won the game
if (!document.querySelectorAll('.card:not(.flipped)').length) {
setTimeout(() => {
selectors.boardContainer.classList.add('flipped');
selectors.win.innerHTML = `
<span class="win-text">
You won!<br />
with <span class="highlight">${state.totalFlips}</span> moves<br />
under <span class="highlight">${state.totalTime}</span> seconds
</span>
`;
clearInterval(state.loop);
}, 1000);
}
}
const attachEventListeners = () => {
document.addEventListener('click', event => {
const eventTarget = event.target;
const eventParent = eventTarget.parentElement;
if (eventTarget.className.includes('card') && !eventParent.className.includes('flipped')) {
flipCard(eventParent);
} else if (eventTarget.nodeName === 'BUTTON' && !eventTarget.className.includes('disabled')) {
startGame();
}
})
}
generateGame();
attachEventListeners();
html {
width: 100%;
height: 100%;
background: linear-gradient(325deg, #6f00fc 0%, #fc7900 50%, #fcc700 100%);
font-family: Fredoka;
}
.game {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
.controls {
display: flex;
gap: 20px;
margin-bottom: 20px;
}
button {
background: #282A3A;
color: #FFF;
border-radius: 5px;
padding: 10px 20px;
border: 0;
cursor: pointer;
font-family: Fredoka;
font-size: 18pt;
}
.disabled {
color: #757575;
}
.stats {
color: #FFF;
font-size: 14pt;
}
.board-container {
position: relative;
}
.board,
.win {
border-radius: 5px;
box-shadow: 0 25px 50px rgb(33 33 33 / 25%);
background: linear-gradient(135deg, #6f00fc 0%, #fc7900 50%, #fcc700 100%);
transition: transform .6s cubic-bezier(0.4, 0.0, 0.2, 1);
backface-visibility: hidden;
}
.board {
padding: 20px;
display: grid;
grid-template-columns: repeat(4, auto);
grid-gap: 20px;
}
.board-container.flipped .board {
transform: rotateY(180deg) rotateZ(50deg);
}
.board-container.flipped .win {
transform: rotateY(0) rotateZ(0);
}
.card {
position: relative;
width: 100px;
height: 100px;
cursor: pointer;
}
.card-front,
.card-back {
position: absolute;
border-radius: 5px;
width: 100%;
height: 100%;
background: #282A3A;
transition: transform .6s cubic-bezier(0.4, 0.0, 0.2, 1);
backface-visibility: hidden;
}
.card-back {
transform: rotateY(180deg) rotateZ(50deg);
font-size: 28pt;
user-select: none;
text-align: center;
line-height: 100px;
background: #FDF8E6;
}
.card.flipped .card-front {
transform: rotateY(180deg) rotateZ(50deg);
}
.card.flipped .card-back {
transform: rotateY(0) rotateZ(0);
}
.win {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
text-align: center;
background: #FDF8E6;
transform: rotateY(180deg) rotateZ(50deg);
}
.win-text {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 21pt;
color: #282A3A;
}
.highlight {
color: #6f00fc;
}
img {
width: 100%;
height: 100%;
object-fit: cover;
}
<div class="game">
<div class="controls">
<button>Start</button>
<div class="stats">
<div class="moves">0 moves</div>
<div class="timer">time: 0 sec</div>
</div>
</div>
<div class="board-container">
<div class="board" data-dimension="4"></div>
<div class="win">You won!</div>
</div>
</div>

Loop through elements by class name, click any element within the array, but only affect the element clicked?

I am working on a WordPress site and I have a snippet of html that iterates with repeating classes.
I am attempting to create a click function but only affect the element that is clicked. All in JavaScript.
As of right now my function is affecting all elements with the class name. Test code can be found at my CodePen or below.
I can accomplish this without nested loops as seen here. So my assumption is the problem lies within the second forEach loop. I would appreciate any light on the matter.
Thank you in advance.
/**
*Constructors
**/
const carousel = document.getElementsByClassName("carousel");
const btns = document.getElementsByClassName("btns");
/**
*Execute
**/
Array.from(btns).forEach((i) => {
i.addEventListener("click", (e) => {
Array.from(carousel).forEach((n) => {
if (i.classList.contains("slide-left")) {
n.scrollLeft -= 20;
} else if (i.classList.contains("slide-right")) {
n.scrollLeft += 20;
} else {
alert("ut oh");
}
});
});
});
/*
**Utilities
*/
/*containers*/
.feed-container {
position: absolute;
height: 200px;
width: 100%;
display: grid;
grid-template-columns: 1;
grid-template-rows: 1;
}
.carousel {
grid-row: 1;
grid-column: 1/5;
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-template-rows: 1;
grid-gap: 15px;
align-self: center;
border: 1px solid #ccc;
overflow-x: scroll;
overflow-y: hidden;
}
/*div-buttons*/
div[class*="slide-"] {
/*opacity: 0;*/
position: sticky;
grid-row: 1;
z-index: 5;
place-self: center;
transition: 0.5s;
padding: 15px;
}
.slide-left {
grid-column: 1;
}
.slide-right {
grid-column: 4;
}
/*items*/
div[class*="item-"] {
grid-row: 1;
width: 400px;
height: 200px;
}
.item-1 {
background: blue;
}
.item-2 {
background: red;
}
.item-3 {
background: grey;
}
.item-4 {
background: yellow;
}
/*scrollbar*/
::-webkit-scrollbar {
display: none;
}
/*chevrons*/
[class*="chevron-"] {
box-sizing: border-box;
position: relative;
display: block;
transform: scale(var(--ggs, 1));
width: 22px;
height: 22px;
border: 2px solid transparent;
border-radius: 25px;
}
[class*="chevron-"]::after {
content: "";
display: block;
box-sizing: border-box;
position: absolute;
width: 40px;
height: 40px;
border-bottom: 8px solid;
border-left: 8px solid;
bottom: 0;
}
.chevron-left::after {
transform: rotate(45deg);
left: 15px;
}
.chevron-right::after {
transform: rotate(-135deg);
right: 15px;
}
/*
**Exceptions
*/
.btns:hover {
cursor: pointer;
}
.opaque {
opacity: 1 !important;
}
.show {
display: block;
}
<div id="wrapper" style="display:grid; grid-template-rows:repeat(2, auto); grid-gap: 100px;">
<div>
<h1>Header</h1>
<div class="feed-container">
<div class="carousel">
<div class="item-1"></div>
<div class="item-2"></div>
<div class="item-3"></div>
<div class="item-4"></div>
</div>
<div class="slide-left btns">
<div class="chevron-left"></div>
</div>
<div class="slide-right btns">
<div class="chevron-right"></div>
</div>
</div>
</div>
<br>
<div>
<h1>Header</h1>
<div class="feed-container">
<div class="carousel">
<div class="item-1"></div>
<div class="item-2"></div>
<div class="item-3"></div>
<div class="item-4"></div>
</div>
<div class="slide-left btns">
<div class="chevron-left"></div>
</div>
<div class="slide-right btns">
<div class="chevron-right"></div>
</div>
</div>
</div>
</div>
It's because you're getting all the elements with class name carousel and then looping through them with each click.
const carousel = document.getElementsByClassName("carousel");
Instead what you need to do is get the carousels only under the button's parent when you trigger the click event
eg something like this:
Array.from(btns).forEach((i) => {
i.addEventListener("click", (e) => {
const targetElement = e?.target || e?.srcElement;
const parent = targetElement.parentElement();
const carousel = Array.from(parent.getElementsByClassName("carousel"));
carousel.forEach((n) => {
if (i.classList.contains("slide-left")) {
n.scrollLeft -= 20;
} else if (i.classList.contains("slide-right")) {
n.scrollLeft += 20;
} else {
alert("ut oh");
}
});
});
});
I took a look at the following as recommend and it seems to do the trick.
I created a variable that calls the parentNode "forEach()" button clicked. Oppose to looping through each element.
Working example, codePen
const carousel = document.querySelectorAll(".carousel");
const btns = document.querySelectorAll(".btns");
btns.forEach((i) => {
i.addEventListener("click", () => {
var x = i.parentNode;
var y = Array.from(x.querySelectorAll(".carousel"));
y.forEach((n) => {
if (i.classList.contains("slide-left")) {
n.scrollLeft -= 20;
} else {
n.scrollLeft += 20;
}
});
});
});

How do I stop css rotate back to face before expected animation?

I'm trying to simulate the animation of flips of a coin with JS & CSS.
I guess the keys are transform-style, backface-visibility, rotateY, animation-fill-mode and transform in CSS as well as Math.random in JS.
If the coin is the heads, everything is OK.
If the coin is tail, clicking the button will flip it to head and then start the expected flipping animation.
How do I make it start flipping animation directly from the tail?
const coin = document.querySelector('#coin');
const button = document.querySelector('#flip');
const status = document.querySelector('#status');
const heads = document.querySelector('#headsCount');
const tails = document.querySelector('#tailsCount');
let headsCount = 0;
let tailsCount = 0;
function deferFn(callback, ms) {
setTimeout(callback, ms);
}
function processResult(result) {
if (result === 'heads') {
headsCount++;
heads.innerText = headsCount;
} else {
tailsCount++;
tails.innerText = tailsCount;
}
status.innerText = result.toUpperCase();
}
function flipCoin() {
coin.setAttribute('class', '');
const random = Math.random();
const result = random < 0.5 ? 'heads' : 'tails';
deferFn(function() {
coin.setAttribute('class', 'animate-' + result);
deferFn(processResult.bind(null, result), 2900);
}, 100);
}
button.addEventListener('click', flipCoin);
h2 {
margin: .25rem;
}
div.container {
margin: auto;
display: flex;
flex-direction: column;
align-items: center;
}
button {
padding: 1rem;
background-color: skyblue;
}
#coin {
position: relative;
width: 15rem;
height: 15rem;
margin: 2rem 0rem;
transform-style: preserve-3d;
}
#coin div {
width: 100%;
height: 100%;
border: 2px solid black;
border-radius: 50%;
backface-visibility: hidden;
background-size: contain;
position: absolute;
}
.heads {
background-image: url("https://en.numista.com/catalogue/photos/inde/2311-original.jpg");
}
.animate-heads {
animation: flipHeads 3s;
animation-fill-mode: forwards;
}
#keyframes flipHeads {
from {
transform: rotateY(0deg);
}
to {
transform: rotateY(1800deg);
}
}
.tails {
background-image: url("https://en.numista.com/catalogue/photos/inde/3165-original.jpg");
transform: rotateY(-180deg);
}
.animate-tails {
animation: flipTails 3s;
animation-fill-mode: forwards;
}
#keyframes flipTails {
from {
transform: rotateY(0deg);
}
to {
transform: rotateY(1620deg);
}
}
<div class='container'>
<h2>Confused about your life decision? Just flip this coin!</h2>
<h2>Btw, don't forget to assign something to both sides.</h2>
<p>And don't take your life decision based on this stupid coin flip. I was kidding.</p>
<div id="coin" class=''>
<div id="heads" class="heads"></div>
<div id="tails" class="tails"></div>
</div>
<button id="flip">Flip this thing</button>
<p>Heads: <span id="headsCount">0</span> Tails: <span id="tailsCount">0</span></p>
<p><span id="status"></span></p>
</div>
You can use the css property:
animation-fill-mode: forwards;

Both buttons work together .. - javascript

I'm trying to have only one button working at one time .. and it does not work in any way ..
Although I do listener on both separately, and ID changed ..
I tried different options and could not reach a solution
help please
https://codepen.io/ido4560/pen/dZxqvZ
html
<div class="buttons">
<button id="buttonStart">Start Game</button>
<button id="buttonReset">Reset</button>
</div>
<div id="main">
<div id="first">
<div data-il="The Zombie" class="anim1"></div>
</div>
<div id="second">
<div data-il="The Man" class="anim2"></div>
</div>
<div id="third">
<div data-il="The Woman" class="anim3"></div>
</div>
</div>
<span>Score: </span>
css
#keyframes zombieWalk {
0% {
background-position: 0px 0px;
}
100% {
background-position: 1191px 0px;
}
}
#keyframes manWalk {
0% {
background-position: -5px 2px;
}
100% {
background-position: -993px 6px;
}
}
#keyframes womanWalk {
0% {
background-position: -21px 0px;
}
100% {
background-position: -1020px 0px;
}
}
body {
background-color: black;
margin: 10px auto;
width: 1000px;
text-align: center;
}
#main {
border: 2px solid white;
}
#main>#first {
background-color: red;
height: 179px;
}
#main>#first>div {
height: 167px;
width: 133px;
background: url(zombie.png) 0px 0px;
background-size: 1323px 168px;
background-color:white;
}
#main>#second {
background-color: blue;
height: 173px;
}
#main>#second>div {
height: 162px;
width: 133px;
background: url(man.png) -5px 2px;
background-size: 1126px 163px;
background-color:white;
}
#main>#third {
background-color: yellow;
height: 168px;
}
#main>#third>div {
height: 167px;
width: 133px;
background: url(woman.png) -21px 0px;
background-size: cover;
background-color:white;
}
#main>#first>.zombieWalk {
animation: zombieWalk 1s steps(9) infinite;
transform: translate(860px, 0px);
animation-play-state: running;
}
#main>#second>.manWalk {
animation: manWalk 1s steps(7) infinite;
transform: translate(860px, 0px);
animation-play-state: running;
}
#main>#third>.womanWalk {
animation: womanWalk 1s steps(6) infinite;
transform: translate(860px, 0px);
animation-play-state: running;
}
.buttons>button {
font-size: 20px;
margin: 10px 50px 20px 50px;
}
span {
color: red;
font-weight: bold;
line-height: 20px;
font-family: arial;
}
abcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgabcdefgab
js
/* Global var */
var div = document.querySelectorAll("#main>div>div");
var flag = 1;
var span = document.querySelector("span");
function runGame() {
var buttonStart = document.getElementById("buttonStart");
console.log(buttonStart);
buttonStart = addEventListener("click", startGame);
var buttonReset = document.getElementById("buttonReset");
console.log(buttonReset);
buttonReset = addEventListener("click", startAgain);
var number = rndNumber();
div[0].style.transition = 'transform ' + (rndNumber()) + 'ms';
//console.log(number);
var number = rndNumber();
div[1].style.transition = 'transform ' + (rndNumber()) + 'ms';
//console.log(number);
var number = rndNumber();
div[2].style.transition = 'transform ' + (rndNumber()) + 'ms';
//console.log(number);
div[0].addEventListener("transitionend", animEnd);
div[1].addEventListener("transitionend", animEnd);
div[2].addEventListener("transitionend", animEnd);
}
function animEnd(e) {
//console.log('im end');
//console.log(e);
console.log(flag);
if (flag == 1) {
var player = e.target.getAttribute("data-il");
//console.log(player + ' first');
span.innerHTML += "<br>";
span.innerText += '#1 ' + player;
//console.log(span);
flag++;
} else if (flag == '2') {
var player = e.target.getAttribute("data-il");
//console.log(player + ' second');
span.innerHTML += "<br>";
span.innerText += '#2 ' + player;
span.innerHTML += "<br>";
flag++;
} else if (flag == '3') {
var player = e.target.getAttribute("data-il");
//console.log(player + ' third');
span.innerText += '#3 ' + player;
}
}
function startGame() {
div[0].className = "zombieWalk";
div[1].className = "manWalk";
div[2].className = "womanWalk";
}
function rndNumber() {
return Math.floor(Math.random() * 10000);
}
function startAgain(){
window.location.reload(false);
}
runGame();
I played around with your code a bit on codepen. Though your question is
a bit vague, but I suspect that the issue you are having is that you are doing:
buttonStart = addEventListener("click", startGame);
buttonReset = addEventListener("click", startAgain);
Instead of
buttonStart.addEventListener("click", startGame);
buttonReset.addEventListener("click", startAgain);
Explanation: You are supposed to apply event listeners to buttonStart and buttonReset and not equate it to them.

Categories