Issue
I created a small React app that displays Pokemon cards. There's a Card component made up of two divs that represent a front and backside for each card using a card-front
and card-back
className. When a card element is clicked, the card flips from 0%, 50% rotateY(180deg) to 100% rotateY(360deg)
using a transform rotate animation, but the image displays at all times. I'd like for the card to not display the image at the 180deg
rotation, just displaying the card background color, like the backside of a card, then display the new image on the last animation at 360deg
. The way it works now, the card displays the image, it is clicked and the image changes, the card appears to rotate but the image remains on display. What can I do to get the sequence to:
- Click the card then flip from 0 to 180
- The 180deg flip shows a solid color, no image
- The 360deg, last animation, shows the new image.
I tried wrapping the if (card.clicked)…
statement in a setTimeout function and have it run at half the time (0.25s) the full animation takes to run (0.5s). I did that to at least slow down the time the api call would update the image on the card, but it seemed a bit hackish. That also causes some random cards on the page to flip twice. I'm not exactly sure how to get this all to run in sequence smoothly in React.
import { useState, useEffect } from 'react';
import { Scoreboard } from './Scoreboard';
import { Card } from './Card';
function shuffleArray(array) {
let newArray = [...array];
let currentIndex = newArray.length,
randomIndex;
// While there remain elements to shuffle
while (currentIndex !== 0) {
// Pick a remaining element
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
// And swap it with the current element
[newArray[currentIndex], newArray[randomIndex]] = [
newArray[randomIndex],
newArray[currentIndex],
];
}
return newArray;
}
function GamePage() {
const [cards, setCards] = useState([]);
const [currentScore, setCurrentScore] = useState(0);
const [highScore, setHighScore] = useState(0);
const [animationTrigger, setAnimationTrigger] = useState(0);
useEffect(() => {
// Call PokeAPI
fetch('https://pokeapi.co/api/v2/pokemon?limit=12')
.then(response => response.json())
.then(data => {
const cards = data.results.map((pokemon, index) => ({
id: index,
image:
// Poke API image library
`https://github.com/sashafirsov/pokeapi-sprites/blob/master/sprites/pokemon/other/dream-world/${
index + 1
}.svg?raw=true` || '../images/pokeball.svg',
}));
setCards(cards);
});
}, []);
function handleCardClick(id) {
// Find the card that was clicked
setAnimationTrigger(count => count + 1);
const card = cards.find(card => card.id === id);
const shuffledCards = shuffleArray([...cards]);
if (card.clicked) {
// Reset the game if the same card was clicked
setCards(shuffledCards.map(card => ({ ...card, clicked: false })));
setCurrentScore(0);
} else {
// Update the card to clicked true
setCards(
shuffledCards.map(card =>
card.id === id ? { ...card, clicked: true } : card
)
);
setCurrentScore(currentScore + 1);
// Update the high score
if (currentScore + 1 > highScore) {
setHighScore(currentScore + 1);
}
}
}
return (
<>
<h1>Let's Play</h1>
<Scoreboard currentScore={currentScore} highScore={highScore} />
<div className='game-board'>
{cards.map(card => (
<Card
key={`${card.id}-${animationTrigger}`}
image={card.image}
onClick={() => handleCardClick(card.id)}
className='card-shuffle-animation'
/>
))}
</div>
</>
);
}
export { GamePage };
import PropTypes from 'prop-types';
function Card({ image, onClick, className }) {
return (
<div className={`card ${className}`} onClick={onClick}>
<div className='card-inner'>
<div className='card-front'>
<img src={image} className='card-img' alt='Pokemon' />
</div>
<div className='card-back'></div>
</div>
</div>
);
}
Card.propTypes = {
image: PropTypes.string.isRequired,
onClick: PropTypes.func.isRequired,
className: PropTypes.string.isRequired,
};
export { Card };
/* Card */
.card {
display: flex;
justify-content: center;
align-items: center;
background-color: var(--poke-brown);
border-radius: 8px;
padding: 0.5em;
margin: 0 auto;
width: 150px;
box-shadow: 0 0 2em rgba(0, 0, 0, 0.25);
}
.card-img {
max-width: 125px;
height: 125px;
}
@keyframes shuffle {
0% {
transform: rotateY(0deg);
}
50% {
transform: rotateY(180deg);
}
100% {
transform: rotateY(360deg);
}
}
.card-shuffle-animation {
animation: shuffle 0.5s ease forwards;
}
.card:hover {
filter: drop-shadow(0 0 2em var(--light-bg));
cursor: pointer;
}
Solution
Your component background is not covering your parent div, I gave position: absolute
to fix this and another animation to manage visibility of card-back
as you want to show it just in 180deg
Here is the css
.card {
display: flex;
justify-content: center;
align-items: center;
position: relative;
width: 150px;
height: 200px;
perspective: 1000px;
background-color: var(--poke-brown);
border-radius: 8px;
box-shadow: 0 0 2em rgba(0, 0, 0, 0.25);
}
.card-inner {
width: 100%;
height: 100%;
transform-style: preserve-3d;
transition: transform 0.5s ease;
animation: shuffle 0.5s ease forwards;
}
@keyframes shuffle {
0% {
transform: rotateY(0deg);
}
50% {
transform: rotateY(180deg);
}
100% {
transform: rotateY(360deg);
}
}
.card-front,
.card-back {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
width: 100%;
height: 100%;
backface-visibility: hidden;
}
.card-img {
max-width: 125px;
height: 125px;
transition: visibility 0.25s ease;
}
.card-back {
transform: rotateY(180deg);
background-color: black;
opacity: 0;
animation: showBackground 0.5s ease forwards;
}
.card:hover {
filter: drop-shadow(0 0 2em var(--light-bg));
cursor: pointer;
}
@keyframes showBackground {
0% {
opacity: 0;
}
50% {
opacity: 1;
}
100% {
opacity: 0;
}
}
.card-front {
background-color: var(--poke-brown);
border-radius: 8px;
}
.card-back {
border-radius: 8px;
}
and working prototype for css part (I did not change your react code since it was a css question) https://codesandbox.io/p/sandbox/affectionate-fog-wk9szz
Answered By - Evren
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.