Creating an animation for a basic React slot machine

Viewed 4830

I'm quite new to react and a simple slot machine is my first mini-project. Currently I have completed the logic for displaying random emojis after the button is pressed. The next step for me, before styling and adding logic for winning/losing and a counter for coins etc is adding an animation.

https://codepen.io/fmressel/pen/vRLerN

This codepen is exactly the sort of thing I'm after, but as you can see it is structured quite differently to my code (below), and I'm pulling my hair out trying to figure out how I can get something similar to work for my project.

Any help much appreciated!

import React, { Component } from 'react'
import Contents from './Contents'
import './mainslots.css'


class Slots extends Component {
    static defaultProps = {
        fruits: ["", "", "", "", "", ""]
    };

    

    constructor(props) {
        super(props);
        this.state = {fruit1: '', fruit2: '', fruit3: '', rolling: false};
        this.roll = this.roll.bind(this);
    };

    roll = () => {
        const dFruit1 = this.props.fruits[
            Math.floor(Math.random() * this.props.fruits.length)
        ];
        const dFruit2 = this.props.fruits[    
            Math.floor(Math.random() * this.props.fruits.length)
        ];

        const dFruit3 = this.props.fruits[    
            Math.floor(Math.random() * this.props.fruits.length)
        ];


        this.setState({fruit1: dFruit1, fruit2: dFruit2, fruit3: dFruit3, rolling: true});
        setTimeout(() => {
            this.setState({ rolling: false });
        }, 700)
    }
    

    render(){
        return(
            <div className="SlotMachine">
                <div className="SlotsContainer">
                {this.state.fruit1}
                {this.state.fruit2}
                {this.state.fruit3}
                </div>

                <button className="spinner" onClick={this.roll} disabled={this.state.rolling}>
                {this.state.rolling ? "Spinning..." : "Spin"}
                </button>
            </div>
        );
    }
}

export default Slots;

import React, { Component } from 'react'

class Contents extends Component {

    Fruits = ["", "", "", "", "", ""];


    render() {
        return(
            <div className="emptys">
                {this.props.roll}

            </div>
        )
    }
}

export default Contents

2 Answers

Here you go,

It was fun to develop :), you can run the below code snippet to review and I've added the comments in code, that will make things clear, please have a look,

Hope this will help,

const { createRef , Component } = React;

class Slots extends Component {
  static defaultProps = {
    fruits: ["", "", "", "", "", ""]
  };

  constructor(props) {
    super(props);
    this.state = { fruit1: "", fruit2: "", fruit3: "", rolling: false };

    // get ref of dic onn which elements will roll
    this.slotRef = [createRef(), createRef(), createRef()];
  }

  // to trigger roolling and maintain state
  roll = () => {
    this.setState({
      rolling: true
    });
    setTimeout(() => {
      this.setState({ rolling: false });
    }, 700);

    // looping through all 3 slots to start rolling
    this.slotRef.forEach((slot, i) => {
      // this will trigger rolling effect
      const selected = this.triggerSlotRotation(slot.current);
      this.setState({ [`fruit${i + 1}`]: selected });
    });

  };

  // this will create a rolling effect and return random selected option
  triggerSlotRotation = ref => {
    function setTop(top) {
      ref.style.top = `${top}px`;
    }
    let options = ref.children;
    let randomOption = Math.floor(
      Math.random() * Slots.defaultProps.fruits.length
    );
    let choosenOption = options[randomOption];
    setTop(-choosenOption.offsetTop + 2);
    return Slots.defaultProps.fruits[randomOption];
  };

  render() {
    return (
      <div className="SlotMachine">
        <div className="slot">
          <section>
            <div className="container" ref={this.slotRef[0]}>
              {Slots.defaultProps.fruits.map((fruit, i) => (
                <div key={i}>
                  <span>{fruit}</span>
                </div>
              ))}
            </div>
          </section>
        </div>
        <div className="slot">
          <section>
            <div className="container" ref={this.slotRef[1]}>
              {Slots.defaultProps.fruits.map(fruit => (
                <div>
                  <span>{fruit}</span>
                </div>
              ))}
            </div>
          </section>
        </div>
        <div className="slot">
          <section>
            <div className="container" ref={this.slotRef[2]}>
              {Slots.defaultProps.fruits.map(fruit => (
                <div>
                  <span>{fruit}</span>
                </div>
              ))}
            </div>
          </section>
        </div>
        <div
          className={!this.state.rolling ? "roll rolling" : "roll"}
          onClick={!this.state.rolling && this.roll}
          disabled={this.state.rolling}
        >
          {this.state.rolling ? "Rolling..." : "ROLL"}
        </div>
      </div>
    );
  }
}

ReactDOM.render(<Slots />, document.getElementById('react-root'));
.App {
  font-family: sans-serif;
  text-align: center;
}

.slot {
  position: relative;
  display: inline-block;
  height: 100px;
  width: 80px;
}

section {
  position: absolute;
  border-radius: 15px !important;
  border: 3px solid black !important;
  width: 70px;
  height: 70px;
  overflow: hidden;
  background-color: grey;
  border-radius: 2px;
  border: 1px solid lightgrey;
  color: white;
  font-family: sans-serif;
  text-align: center;
  font-size: 25px;
  line-height: 60px;
  cursor: default;
}

.container {
  position: absolute;
  top: 2px;
  width: 100%;
  transition: top ease-in-out 0.5s;
  text-align: center;
}

.roll {
  width: 215px;
  cursor: pointer;
  background-color: yellow;
  padding: 10px;
  text-align: center;
  font-size: 20px;
  border-radius: 20px;
  border: 3px solid black;
}

.rolling {
  animation: blinkingText 1.2s infinite;
}

@keyframes blinkingText {
  0% {
    color: #000;
  }
  49% {
    color: #000;
  }
  60% {
    color: transparent;
  }
  99% {
    color: transparent;
  }
  100% {
    color: #000;
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react-root"></div>

I recreated the @VivekDoshi 's answer as a react FC.

const { useRef, useState} = React;

function Slots(){
  const [fruit1,setFruit1] = useState("");
  const [fruit2,setFruit2] = useState("");
  const [fruit3,setFruit3] = useState("");
  const [rolling,setRolling] = useState(false);
  let slotRef = [useRef(null), useRef(null), useRef(null)];
  const fruits = ["", "", "", "", "", ""]
  
  // to trigger roolling and maintain state
  const roll = () => {
    setRolling(true);
    setTimeout(() => {
      setRolling(false);
    }, 700);

    // looping through all 3 slots to start rolling
    slotRef.forEach((slot, i) => {
      // this will trigger rolling effect
      const selected = triggerSlotRotation(slot.current);
      if(i+1 == 1)
        setFruit1(selected);
      else if(i+1 == 2)
        setFruit2(selected);
      else 
        setFruit3(selected);
     });
  };
  
  // this will create a rolling effect and return random selected option
  const triggerSlotRotation = ref => {
    function setTop(top) {
      ref.style.top = `${top}px`;
    }
    let options = ref.children;
    let randomOption = Math.floor(
      Math.random() * fruits.length
    );
    let choosenOption = options[randomOption];
    setTop(-choosenOption.offsetTop + 2);
    return fruits[randomOption];
  };

  return (
      <div className="SlotMachine">
        <div className="slot">
          <section>
            <div className="container" ref={slotRef[0]}>
              {fruits.map((fruit, i) => (
                <div key={i}>
                  <span>{fruit}</span>
                </div>
              ))}
            </div>
          </section>
        </div>
        <div className="slot">
          <section>
            <div className="container" ref={slotRef[1]}>
              {fruits.map(fruit => (
                <div>
                  <span>{fruit}</span>
                </div>
              ))}
            </div>
          </section>
        </div>
        <div className="slot">
          <section>
            <div className="container" ref={slotRef[2]}>
              {fruits.map(fruit => (
                <div>
                  <span>{fruit}</span>
                </div>
              ))}
            </div>
          </section>
        </div>
        <div
          className={!rolling ? "roll rolling" : "roll"}
          onClick={!rolling && roll}
          disabled={rolling}>
          {rolling ? "Rolling..." : "ROLL"}
        </div>
      </div>
    ); 
};

ReactDOM.render(<Slots />, document.getElementById('react-root'));
.App {
  font-family: sans-serif;
  text-align: center;
}

.slot {
  position: relative;
  display: inline-block;
  height: 100px;
  width: 80px;
}

section {
  position: absolute;
  border-radius: 15px !important;
  border: 3px solid black !important;
  width: 70px;
  height: 70px;
  overflow: hidden;
  background-color: grey;
  border-radius: 2px;
  border: 1px solid lightgrey;
  color: white;
  font-family: sans-serif;
  text-align: center;
  font-size: 25px;
  line-height: 60px;
  cursor: default;
}

.container {
  position: absolute;
  top: 2px;
  width: 100%;
  transition: top ease-in-out 0.5s;
  text-align: center;
}

.roll {
  width: 215px;
  cursor: pointer;
  background-color: yellow;
  padding: 10px;
  text-align: center;
  font-size: 20px;
  border-radius: 20px;
  border: 3px solid black;
}

.rolling {
  animation: blinkingText 1.2s infinite;
}

@keyframes blinkingText {
  0% {
    color: #000;
  }
  49% {
    color: #000;
  }
  60% {
    color: transparent;
  }
  99% {
    color: transparent;
  }
  100% {
    color: #000;
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react-root"></div>

Related