简体   繁体   中英

I am trying to animate an array of letters in my React Hangman App

Problem

I am trying to use react-spring to have letters from an array animate in and out for my Hangman App. I want them to fade in on load, and fade out when removed from the array. They are removed from the array on click. For now I just want them to fade in and out. The closest I got was the same buttons as before, but it was laggy and didn't animate at all.

I have tried using Spring components, Transition Components, and useTransition.

This is the most recent example I was trying to work off of. codesandbox

APP --- didn't include all of the useState functions or helper functions for readability

const App = () => {
const [availableLetters, setAvailableLetters] = useState([
    'a',
    'b',
    'c',
    'd',
    'e',
    'f',
    'g',
    'h',
    'i',
    'j',
    'k',
    'l',
    'm',
    'n',
    'o',
    'p',
    'q',
    'r',
    's',
    't',
    'u',
    'v',
    'w',
    'x',
    'y',
    'z'
  ])
 const location = useLocation()

  const transitions = useTransition(location, location => location.pathname, {
    from: { opacity: 0, transform: 'translate(100%,0)' },
    enter: { opacity: 1, transform: 'translate(0,0)' },
    leave: { opacity: 0, transform: 'translate(-50%,0)' }
  })
return (
    <Fragment>
      <Header defaultGuesses={defaultGuesses} />
      {msgAlerts.map((msgAlert, index) => (
        <AutoDismissAlert
          key={index}
          heading={msgAlert.heading}
          variant={msgAlert.variant}
          message={msgAlert.message}
        />
      ))}
      {/* routes */}
      <main className="container">
        {transitions.map(({ item, props, key }) => (
          <animated.div native='true' key={key} style={props}>
            <Switch location={item}>
              {/* home */}
              <Route
                exact
                path="/"
                render={() => (
                  <Welcome
                    msgAlert={msgAlert}
                    resetAllButSecret={resetAllButSecret}
                    setSecret={setSecret}
                  />
                )}
              />

              <Route
                exact
                path="/guesses"
                render={() => (
                  <Guesses
                    msgAlert={msgAlert}
                    secret={secret}
                    setGuesses={setGuesses}
                    resetAllButSecretAndGuesses={resetAllButSecretAndGuesses}
                    setDefaultGuesses={setDefaultGuesses}
                  />
                )}
              />
              <Route
                exact
                path="/play"
                render={() => (
                  <Play
                    guesses={guesses}
                    availableLetters={availableLetters}
                    correctLetters={correctLetters}
                    incorrectLetters={incorrectLetters}
                    secret={secret}
                    setSecret={setSecret}
                    pushToCorrect={pushToCorrect}
                    pushToIncorrect={pushToIncorrect}
                    removeAvailable={removeAvailable}
                    msgAlert={msgAlert}
                    resetBoard={resetBoard}
                    gameOver={gameOver}
                    setGameOver={setGameOver}
                    setGuesses={setGuesses}
                    resetGame={resetGame}
                  />
                )}
              />
            </Switch>
          </animated.div>
        ))}
      </main>
    </Fragment>
  )
}

export default App

Relevant Play.js code. This is what I'm trying right now but nothing is even showing up, no letters at all

import { Spring, animated, Transition } from 'react-spring/renderprops'
import ClickableLetter from './ClickableLetter'
const AnimatedLetter = animated(ClickableLetter)

const Play = ({'tons of props i'm not showing'}) => {
 const availHTML = availableLetters.map((letter, index) => (
    <Transition
      from={{ opacity: 0 }}
      to={{ opacity: 1 }}
      leave={{ opacity: 0 }}
      // config={config.slow}
      key={`${letter}_${index}`}
    >
      {styles => (

        <AnimatedLetter
          style={styles}
          pushToCorrect={pushToCorrect}
          pushToIncorrect={pushToIncorrect}
          secret={secret}
          removeAvailable={removeAvailable}
          letter={letter}
          gameOver={gameOver}
          msgAlert={msgAlert}
        />

      )}
    </Transition>
  ))

return (
    <AbsoluteWrapper>
      <Fragment>
    {availHTML}
</Fragment>
</AbsoluteWrapper>
)
}

Original Working Code

 const availHtml = availableLetters.map(letter => (
    <ClickableLetter
      key={letter}
      pushToCorrect={pushToCorrect}
      pushToIncorrect={pushToIncorrect}
      secret={secret}
      removeAvailable={removeAvailable}
      letter={letter}
      gameOver={gameOver}
      msgAlert={msgAlert}
    />
  ))

ClickableLetter.js

import React from 'react'
import { withRouter } from 'react-router-dom'
import { PrimaryButton } from '../Shared/Styled'
// these are the green letters used to let a user guess a letter

const ClickableLetter = ({
  letter, secret, pushToCorrect, pushToIncorrect, removeAvailable, gameOver, msgAlert
}) => {
  // this function is used to either push the value to a correct or incorrect array causing an app wide state change and re render
  const pushValue = () => {
    if (!gameOver) {
      if (secret.toLowerCase().includes(letter)) {
        pushToCorrect(letter)
      } else {
        pushToIncorrect(letter)
      }
      // take it from the availLetter arr to re-render this component with one less letter
      removeAvailable(letter)
    } else {
      msgAlert({
        heading: 'Oops!',
        message: 'Game is over, please click reset to play again',
        variant: 'danger'
      })
    }
  }
  return (

    <PrimaryButton onClick={pushValue}>{letter}</PrimaryButton>

  )
}

export default withRouter(ClickableLetter)

Other Attempts


  const transitions = useTransition(availableLetters, item => item, {
    from: { opacity: 0},
    enter: { opacity: 1},
    leave: { opacity: 0}
  })

  const availHTML = transitions.map(({ item, props, key }) =>
    <AnimatedLetter key={key} style={props}
      pushToCorrect={pushToCorrect}
      pushToIncorrect={pushToIncorrect}
      secret={secret}
      removeAvailable={removeAvailable}
      letter={item}
      gameOver={gameOver}
      msgAlert={msgAlert}/>
  )

My other attempt was using an animated.div instead of animated(ClickableLetter) with the ClickableLetter rendered inside of the div. Something like this. (Don't have the code so this isn't exact)

const availHTML = transitions.map(({ item, props, key }) =>
    <animated.div key={key} style={props}><ClickableButton letter={item}/> </animated.div>
      
  ))

Okay I figured it out after a while. First I needed to make my custom component animation ready...

 const AnimatedClickableLetter = animated(ClickableLetter)

Then I used the Transition component, I still haven't figured out how to do this with useTransition. I tried but it wasn't getting anywhere.

 const availHTML = (
    <Transition
      items={availableLetters} // the array
      keys={item => item} //unique keys, in this instance, the letter is fine
      from={{ opacity: 1 }} // animation props
      enter={{ opacity: 1 }}
      leave={{ opacity: 0 }}
    >
      {letter => props => //for each letter, make an AnimatedClickableLetter..
          //props will be the animation style.
        <AnimatedClickableLetter
          style={props} //here's where we pass it in as props.
          pushToCorrect={pushToCorrect}
          pushToIncorrect={pushToIncorrect}
          secret={secret}
          removeAvailable={removeAvailable}
          letter={letter}
          gameOver={gameOver}
          msgAlert={msgAlert}
        />
      }
    </Transition>
  )

Finally, the step that took me forever to figure out... Add the style to the custom component itself.


const ClickableLetter = ({
  letter, secret, pushToCorrect, pushToIncorrect, removeAvailable, gameOver, msgAlert, style //accept the prop here...
}) => {
  // this function is used to either push the value to a correct or incorrect array causing an app wide state change and re render
  const pushValue = () => {
    if (!gameOver) {
      if (secret.toLowerCase().includes(letter)) {
        pushToCorrect(letter)
      } else {
        pushToIncorrect(letter)
      }
      // take it from the availLetter arr to re-render this component with one less letter
      removeAvailable(letter)
    } else {
      msgAlert({
        heading: 'Oops!',
        message: 'Game is over, please click reset to play again',
        variant: 'danger'
      })
    }
  }
  return (
// add the style to the button, here.
    <Fragment>
-      <PrimaryButton onClick={pushValue}>{letter}</PrimaryButton>
+      <PrimaryButton style={style} onClick={pushValue}>{letter}</PrimaryButton>
    </Fragment>
  )
}

export default ClickableLetter

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM