ReactJS typewriter effect inspired by Carmen Sandiego 1991 MS-DOS
Tags: react, webdev, beginners, javascript
Published: 2019-04-25 16:06:07


---
title: ReactJS typewriter effect inspired by Carmen Sandiego 1991 MS-DOS
published: true
description: A ReactJS typewriter effect with sound inspired by the popular videogame Where In The World Is Carmen Sandiego 1991 MS-DOS 
tags: react, webdev, beginners, javascript
cover_image: https://thepracticaldev.s3.amazonaws.com/i/bb4yhivakc7m2imeps4h.jpg
series: Carmen Sandiego with ReactJS
---

**[Spoiler: the result](https://codepen.io/julien180/pen/jRQvrY)**

In the early 90's I used to play at **Where In The World Is Carmen Sandiego** game. Now I'm older, I don't play anymore but I decide to make a clone of the 1991 MS-DOS version with ReactJS. 

## Part 1: HTML and CSS

```html
<div class="cs-instructions">
  <div class="cs-instructions-inner">
      <p>****** FLASH ******</p>
      <p>National treasure stolen from Paris.</p>
      <p>The treasure has been identified as the elevator from the Eiffel Tower.</p>
      <button type="button" onClick="nextStep();">Click to continue</button> 
  </div>
</div>
```

```css 
.cs-instructions {
  background-image: url('https://i.imgur.com/1woliuF.png');
  background-size: contain;
  background-position: bottom left;
  background-repeat: no-repeat;
  position: relative;
  z-index: 1;
  height:342px;
  width: 467px;
} 

.cs-instructions-inner {
  background-color:#fff;
  background-image: url('https://i.imgur.com/6aOej4c.jpg');
  background-size: contain;
  background-position: bottom left;
  background-repeat:repeat-y;
  font-size:14px;
  line-height:30px;
  padding:10px 30px 10px 30px;
  margin:10px 0 0 38px; 
  min-height:120px;
  width:330px;
  overflow-y:hidden;
  position: absolute;
  color:#000;
  z-index: 2;
  bottom:190px;
}
```

Basically we need two `<div>`:
* The first with class name `cs-instructions` is the typewriter machine which has a `position: relative;` property with this [background image](https://i.imgur.com/1woliuF.png) ![bg typewriter](https://i.imgur.com/1woliuF.png)
* And the second with class name `cs-instructions-inner` is the typewriter paper on `position: absolute;bottom:190px;` with this [background image](https://i.imgur.com/6aOej4c.jpg) which is repeat on y axis. ![bg paper](https://i.imgur.com/6aOej4c.jpg)![bg paper](https://i.imgur.com/6aOej4c.jpg)![bg paper](https://i.imgur.com/6aOej4c.jpg) We add a `overflow-y:hidden` to prevent the instructions scrolling.


## Part 2: Typewriter and autoScroll VanillaJS
```html
<div class="cs-instructions">
  <div class="cs-instructions-inner">
      <button type="button" onClick="launch();">Click to start the case</button> 
      <div class="typewriter"> </div>
  </div>
</div>
```
We add a `typewriter` div where the text will be display. The button have a onClick event which call a launch function.  

```javascript
autoScroll = () => {
    const el = document.getElementsByClassName('cs-instructions-inner');
    el.scrollTo(0, el.scrollHeight);
  }

  typeWriter = (string, i=0) => {
    if (i < string.length) {
      document.getElementsByClassName('typewriter')[0].innerHTML += string.charAt(i); // display character one by one
      i++;
      autoScroll;
      setTimeout(() => typeWriter(string, i), 40); // speed
    }
  }
  
 launch = () => {
   var string = '*** FLASH ***';
   string += 'National treasure stolen from Paris.'; 
   string += 'The treasure has been identified as the elevator from the Eiffel Tower.';
   string += 'Female suspect reported at the scene of the crime. Your assignement: Track the thief from Paris to her hideout and arrested her!';
   typeWriter(string);
 } 
 
```
At the bottom we have the **launch()** function. We store text on a string variable and pass it into the _recursive_ **typeWriter()** function with two parameters: string and index which is initialized to 0. We break out of recursive function when the index `i` of the string is equal at the length of the string. Inside the condition we call **autoScroll()** function to ensure we always are at the bottom of the div `cs-instructions-inner`. 

Preview at this step 
{% codepen https://codepen.io/julien180/pen/MRZwGY default-tab=result %}


## Part 3: ReactJs Typewriter component with sound effect
```javascript
class Typewriter extends React.Component {
  constructor(props){
    super(props);
    this.myDiv = null;
    this.mySound = null;
    this.typewriter = element => {
      this.myDiv = element;
    }; 
    this.setmySound = element => {
      this.mySound = element;
    };

    this.playSound = this.playSound.bind(this);
    this.pauseSound = this.pauseSound.bind(this);
  }

  playSound() {
    if (this.mySound && (this.mySound.duration === 0 || this.mySound.paused)) {
      this.mySound.play();
    }
  } 

  pauseSound() { 
    if(this.mySound){
      this.mySound.pause();
    }
  }

  typeWriter = (s, i=0) => {
    if (this.myDiv && i < s.length) {
      this.myDiv.innerHTML += s.charAt(i);
      i++;
      if(i>5) { 
        this.props.setScroll(); 
      }
      this.playSound();
      setTimeout(() => this.typeWriter(s, i), 40);
    }
    else {
       this.pauseSound();
       if(this.props.nextStep){
        this.props.nextStep();
       } 
    }
  }

  componentDidMount() {
    if(this.myDiv) {
      setTimeout(() => this.typeWriter(this.props.mystring), 200);
    }
  }

  componentWillUnmount() {
    this.pauseSound();
  }

  render() {
    return (
      <div className={this.props.mb}> 
        <audio autoPlay preload="auto" ref={this.setmySound} src="https://freesound.org/data/previews/138/138049_2423928-lq.mp3" type="audio/mpeg" > </audio>
        <span ref={this.typewriter}> </span>
      </div>
    );
  }
}
```

Our <Typewriter> component look like `<Typewriter mb={'mb-4'} nextStep={this.nextStep} setScroll={this.setScroll} mystring="National treasure stolen from Paris."></Typewriter>`
  
1. **JSX**: we pass a margin-bottom _mb_ `props` at the div wrapper and inside it we have an `<audio>`tag and a `<span>` tag. Both of them have a `ref` attribute because we need to access and interact with `DOM nodes`.We use the **callback refs** which gives more fine-grain control over when refs are set and unset. 
See more at the official documentation  [https://reactjs.org/docs/refs-and-the-dom.html](https://reactjs.org/docs/refs-and-the-dom.html)
2. **componentDidMount()**: we launch the recursive **typeWriter()** function when the component is mounted. 
3. **Typewriter(s, i=0)** It's not a pure function lol. Lot of things are realized. First the string is passed by the `this.props.mystring` props. Inside typeWriter() function we execute the autoScroll(), we play the sound and we insert characters one by one on the `myDiv ref`. And when the recursive function is breaking out we execute another function `this.props.nextStep()` and also we stop the sound effect `this.pauseSound()`. 
4. **playSound() pauseSound()** sure we add and handle sound effect. 
5. **componentWillUnmount()** we stop the sound when the component is unmounted. 


## Part 4: ReactJs Handle steps component
```javascript
class App extends React.Component {
  constructor(){
    super();
    this.state = {
      step: 0
    }
    this.myDiv = null;
    this.instructions = element => {
      this.myDiv = element;
    };
    this.setScroll = this.setScroll.bind(this);
  }

  setScroll() {
    this.myDiv.scrollTo(0, this.myDiv.scrollHeight);
  }
  
  nextStep = () => this.setState({step: this.state.step + 1});

  render(){
    return(
      <div className="wrapper">
        <div className="cs-instructions">
          <div className="cs-instructions-inner" ref={this.instructions}>
            {
              this.state.step === 0
              ? <button onClick={() => {this.nextStep()}}>CLICK TO START THE CASE</button>
              : ''
            }

            {
              this.state.step > 0 
              ? <Typewriter mb={'mb-4'} nextStep={this.nextStep} setScroll={this.setScroll} mystring="****** FLASH ******"></Typewriter>
              : ''
            }

            {
              this.state.step > 1 
              ? <Typewriter mb={'mb-4'} nextStep={this.nextStep} setScroll={this.setScroll} mystring="National treasure stolen from Paris."></Typewriter>
              : ''
            }

            {
              this.state.step > 2 
              ? <Typewriter mb={'mb-0'} nextStep={this.nextStep} setScroll={this.setScroll} mystring="The treasure has been identified as the elevator from the Eiffel Tower."></Typewriter>
              : ''
            }


            {
              this.state.step > 3 
              ? <button className="mb-4" onClick={() => {this.nextStep()}}>Click to continue</button>
              : ''
            }
            {
              this.state.step > 4 
              ? <Typewriter mb={'mb-0'} nextStep={this.nextStep} setScroll={this.setScroll} mystring="Female suspect reported at the scene of the crime. Your assignement: Track the thief from Paris to her hideout and arrested her!"></Typewriter>
               : ''
            }

             {
              this.state.step > 5 
              ? <button className="mb-4" onClick={() => {this.nextStep()}}>Click to continue</button>
              : ''
            }

            {
              this.state.step > 6 
              ? <Typewriter mb={'mb-0'} setScroll={this.setScroll} mystring="You must apprehend the thief by Sunday, 5 pm. Good luck ! "></Typewriter>
               : ''
            }
          </div>
        </div>
        <div className="credits">Typewriter effect realised with ReactJS inspired by Carmen Sandiego 1991 MS-DOS <br/><a href="https://www.julien-verkest.fr/" target="_blank">Julien Verkest ©️ april 2019 </a> </div>
       </div>
    );
  }
}
```
1. **Constructor()**: we declare a **state** with a `step` init to 0 and we have a `nextStep()` function which increase the number of steps. We have also the `setScroll` function and the `ref instructions`. 
2. **JSX**: Steps are inside conditionals brackets, maybe we need to refactor the code but it's works! 


## Part 5: The result
{% codepen https://codepen.io/julien180/pen/jRQvrY default-tab=result %}