react-typist icon indicating copy to clipboard operation
react-typist copied to clipboard

Add option to restart the animation

Open Laoujin opened this issue 8 years ago • 30 comments

Is it possible to put the typist on a loop? (ie restart after done)

Laoujin avatar Mar 16 '16 19:03 Laoujin

+1

beeant avatar Apr 21 '16 14:04 beeant

Maybe a bit ugly, but this will do the trick:

class Foo extends Component {
  state = {
    typing: true,
  }
  done = () => {
    this.setState({ typing: false }, () => {
      this.setState({ typing: true })
    });
  }
  render() {
    {this.state.typing
      ? <Typist onTypingDone={this.done}>blablabla</Typist>
      : ''
    }
  }
}

webdif avatar Jun 03 '16 10:06 webdif

That works like a charm, thanks!

I changed it to this Component for reusability :)

class RestartingTypist extends Component {
  state = {typing: true}
  done = () => {
    this.setState({ typing: false }, () => {
      setTimeout(() => this.setState({ typing: true }), this.props.timeout || 1200);
    });
  }
  render() {
    const {children, timeout, ...props} = this.props;
    return this.state.typing ?
      <Typist {...props} onTypingDone={this.done}>{children}</Typist>
      : <span>{children}</span>;
  }
}

The difference between : '' and : <span>{children}</span> is that with the first, the text disappears between Typists while the second will keep it displayed. Which one you want probably depends on your taste/requirements :)

Laoujin avatar Jul 18 '16 21:07 Laoujin

hi all! Glad to see it this was solved by composing Typist. I'll look into adding an option to do this automatically. Thanks!

jstejada avatar Jul 20 '16 15:07 jstejada

The only small warning with this approach, is if we have:

      const cursor = {
        hideWhenDone: true,
        blink: false,
        hideWhenDoneDelay: 800,
      };

Then we'll get the warning..

Warning: setState(...): Can only update a mounted or mounting component. This usually means you called setState() on an unmounted component. This is a no-op. Please check the code for the Cursor component.

And I think that is because there is a setTimeout being called in the Cursor component here.

So when the onTypingDone function is called, which removes the Typist component, the Cursor component is no longer mounted to update.

The solution would be to add another setTimeout in the onTypingDone function for the same duration of hideWhenDoneDelay?

magician11 avatar Oct 01 '16 12:10 magician11

@magician11 I've encountered a similar problem. It was because setState was called after the component was unmounted. To handle this, I used clearTimeout function.

Something like:

class RestartingTypist extends Component {
  state = { typing: true }

  done = () => {
    this.setState({ typing: false }, () => {
        this.timeoutes.push(
             setTimeout(() => this.setState({ typing: true }), this.props.timeout || 1200);
        )
    });
  }

  componentWillMount() {
    this.timeouts = [];
  }

  componentWillUnmount() {
    this.timeouts.forEach(window.clearTimeout);
  }

  render() {
    const {children, timeout, ...props} = this.props;
    return this.state.typing ?
      <Typist {...props} onTypingDone={this.done}>{children}</Typist>
      : <span>{children}</span>;
  }
}

caspg avatar Nov 06 '16 18:11 caspg

we might add an option for restart soon!

jstejada avatar Feb 09 '17 07:02 jstejada

Seems like there is still interest in adding a restart feature. Anyone currently working on a PR @jstejada ?

jmknoll avatar Oct 22 '17 08:10 jmknoll

@jstejada any news on a restart feature? I'd be happy to make a PR if no one is working on it.

IAmNatch avatar Nov 27 '17 23:11 IAmNatch

Hi everyone, sorry once again for dragging my feet on this, I've been a bit busy with other stuff. I cleaned up some of Typist's internals a little bit with the recent new features, but haven't had time to look into the restart option. It should be fairly simple to implement!

@IAmNatch (or anyone else) feel free to submit a PR, that would be great! Let me know if you have any questions :)

jstejada avatar Dec 09 '17 19:12 jstejada

@jstejada @IAmNatch After thinking on this a little, I'm not sure that the restart feature in its own right has much of a use case. Perhaps the feature that we want to build here is something that accepts an array of strings and loops over them typing each one out and erasing it. Something like what's being done here. This is pretty similar to the way I've used Typist in my own projects as well. Thoughts?

Also, have started digging into this a bit. Will work on a PR if no one is working on one already.

jmknoll avatar Dec 12 '17 09:12 jmknoll

How is going this feature?

seidelmaycon avatar Dec 20 '17 11:12 seidelmaycon

So it looks like someone has already implemented this feature as a separate package. https://www.npmjs.com/package/react-typist-cycle

Not sure whether this is worth doing if we're just going to be duplicating work. Interested in hearing the package maintainer's thoughts on adding this feature though. @jstejada

jmknoll avatar Dec 21 '17 12:12 jmknoll

@jstejada As you consider restart option - please consider usecases for looping through phrases. Essentially, the library should allow for re-rendering the typist effect if the children change.

oyeanuj avatar Jan 10 '18 21:01 oyeanuj

hmm yeah that's a good point @jmknoll, it seems that restart could work in different ways:

  • Type the words, then immediately start type them again (without a backspace animation)
  • Type the words, then backspace animation, then type them again

I feel like the example in https://www.thinkful.com/, i.e. with a backspace animation and displaying a different word every time, could (and should?) be implemented without a restart option, and can just be implemented with <Typist.Backspace />, e.g. something like:

const words = ['one', 'two', 'three'];

return (
  <Typist>
     Word number: {words.map(word => <>{word}<Typist.Backspace count={word.length} /></>)}
  </Typist>
)

or something like that (didn't actually test that code)

It seems like the simplest possible option would be something like loopIndefinitely (possibly a better name than restart`, which just unmounts Typist and remounts it with the same text and the end of the animation.

You're right though, I'm not sure if that's worth implementing though, given that you could implement it quite easily or use something like react-typist-cycle.

If there's enough interest, maybe we can implement that option. Curious to hear what others think!

jstejada avatar Jan 21 '18 20:01 jstejada

It works by mounting the component again, changing the state of the component.

moonbe77 avatar Feb 16 '18 01:02 moonbe77

I'd like the final code looks like

<Typist loop={true} lastSentenceEffect={fadeOut} >
  <span> First Sentence </span>
  <Typist.Backspace count={8} delay={200} />
  <span> Phrase </span>
</Typist>

jacktang avatar May 04 '18 09:05 jacktang

Any updates on this part?

yluijten avatar May 04 '18 12:05 yluijten

@yluijten 1+

Also react-typist-cycle hasn't been updated in a year ---> https://github.com/rorz/react-typist-cycle

Can we get an update if this is going to be implemented or if someone is working on a PR? or if you guys just want future peeps to use react-typist-cycle

cheers!

SOSANA avatar Jun 11 '18 22:06 SOSANA

I have no current plans to implement this, but happy to take a PR!

jstejada avatar Aug 27 '18 03:08 jstejada

I had a need for this today (I wanted to infinitely loop through an array of phrases with Typist) and have come up with something though not sure how it could be implemented as a feature of the library.

The general idea follows your example @jstejada I had to add a couple of pieces to get it working and I was also concerned about memory management but think I have that covered now.

Here's my code and I'll go over some of the relevant bits at the end -

const commandments = [
  'Never let no one know how much dough you hold',
  'Never let em know your next move',
  'Never trust no-bo-dy',
...
];

class Tablet extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      currentCommandmentIndex: 1,
      commandmentContent: this.contentElement(0),
    };
  }

  contentElement = index => {
    return (
      <Typist key={index} onTypingDone={this.manageContent}>
        {commandments[index]}
        <Typist.Backspace count={commandments[index].length} />
      </Typist>
    );
  };

  generateContent = () => {
    let index = this.state.currentCommandmentIndex;
    if (index > commandments.length - 1) {
      this.setState({ currentCommandmentIndex: 0 });
      return true;
    }

    this.setState({
      currentCommandmentIndex: index + 1,
      commandmentContent: this.contentElement(index),
    });
  };

  manageContent = () => {
    this.generateContent() ? this.generateContent() : null;
  };

  render() {
    return (
          <div className="commandments">
            {this.state.roleContent}
          </div>
.....

So this uses component state to store the current content to render. The content uses the key property on the Typist component to trigger the rerender on setState. The Typist component uses the onTypingDone callback to recursively call the next render of the Typist component by setting state with new content to render.

I added the manageContent method to kill the recursion once the array of phrases had been rendered once. I'm not sure if this is nessecary and the decision to call it after a full loop through the phrase list was arbitrary. I had been running it without this and the memory for the page was slowly creeping up in dev tools but with this it is remaining at a steadyish 1~2MB higher mem usage than the same page without Typist.

Any feedback would be great and if you think this can be implemented as a feature I'd be happy to contribute.

jjmax75 avatar Sep 08 '18 18:09 jjmax75

You can try:

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import Typist from 'react-typist';
import uuid from 'uuid'
import './styles.scss'

export default class Typing extends Component {
  static propTypes = {
    words: PropTypes.array.isRequired
  }

  static defaultProps = {
    words: [
      'affordable',
      'accessible',
      'rewarding'
    ]
  }

  render() {
    const { words } = this.props
    const n = words.map((word, i) => {
      return ([
        <span key={uuid()}>{word}</span>,
        <Typist.Backspace key={uuid()} count={word.length} delay={600} />
      ])
    })

    return [
      <Typist key={uuid()} onTypingDone={() => this.forceUpdate()}>
        {n}
      </Typist>
    ]
  }
}

CharlyJazz avatar Sep 18 '18 20:09 CharlyJazz

This is ugly but works:

const words = ['Reach', 'Engage', 'Impact'];

for (let i = 3; i < 18; i++) {
	words[i] = words[i - 3];
}

function Component() {
	return (
		<Typist>
			{words.map((word, i) => (
				<span key={word}>
					{word}
					<Typist.Backspace
						count={word.length}
						delay={(i + 1) * 1000}
					/>
				</span>
			))}
		</Typist>
	)
}

Of course it is not unlimited but limited by the iteration count in the loop.

protoEvangelion avatar Nov 13 '18 02:11 protoEvangelion

Updating key in onTypingDone rerenders the whole component after typing is complete, restarting the animation:

<Typist
  key={typistIndex}
  onTypingDone={() => this.setState(state => ({ typistIndex: state.typistIndex + 1 }))}
>
  {['foo', 'bar', 'baz'].map(word => ([
    <span>{word}</span>,
    <Typist.Backspace count={word.length} delay={1000} />,
  ]))}
</Typist>

Cinamonas avatar Jan 29 '19 09:01 Cinamonas

Using React Hooks:

type RestartingTypistProps = {
  children: Node,
};

const RestartingTypist = (props: RestartingTypistProps) => {
  const [done, setDone] = useState(false);
  const { children } = props;

  useEffect(() => {
    if (done) {
      setTimeout(() => setDone(false), 250);
    }
  });

  if (done) {
    return <p>&nbsp;</p>;
  }

  return (
    <Typist onTypingDone={() => setDone(true)}>
      {children}
    </Typist>
  );
};

Usage example:

<RestartingTypist>
         occassion
          <Typist.Backspace count={9} delay={500} />
</RestartingTypist>

kendricktan avatar Feb 24 '19 15:02 kendricktan

Updating key in onTypingDone rerenders the whole component after typing is complete, restarting the animation:

<Typist
  key={typistIndex}
  onTypingDone={() => this.setState(state => ({ typistIndex: state.typistIndex + 1 }))}
>
  {['foo', 'bar', 'baz'].map(word => ([
    <span>{word}</span>,
    <Typist.Backspace count={word.length} delay={1000} />,
  ]))}
</Typist>

@Cinamonas I'm trying something like this but the animation does not repeat. I'm unsure why

import React, {Component} from 'react';
import Typist from 'react-typist';

export default class TypeAnimation extends Component {
    typistIndex;
    constructor(props){
        super(props);
        this.state = {
            typistIndex: 0,
        }
    }
    render() {
        return (
            <Typist
              key={this.typistIndex}
              onTypingDone={() => this.setState(state => ({ typistIndex: state.typistIndex + 1 }))}
            >
                {['bank statements.', 'investment reports.'].map(word => ([
                    <span>{word}</span>,
                    <Typist.Backspace count={word.length} delay={1000} />,
                ]))}
            </Typist>
        );
    }
}

havesomeleeway avatar Apr 15 '19 01:04 havesomeleeway

I just created this using react hooks in a stateless function Hope it helps

https://codesandbox.io/s/happy-zhukovsky-rycur

Harshakvarma avatar May 16 '19 00:05 Harshakvarma

I just created this using react hooks in a stateless function Hope it helps

https://codesandbox.io/s/happy-zhukovsky-rycur

instead of use count outside de element, you can put it on a key prop:

<Typist key={countTyping} cursor={{show:false}} avgTypingDelay={50} onTypingDone={() => setCountTyping(0)} >

A more elegant approuch

updated sandbox code https://codesandbox.io/s/react-text-typing-effect-in-infinite-loop-hooks-5yip6

brunoniconeves avatar Jul 14 '20 20:07 brunoniconeves

I got a bettern version

import React, { useState } from "react";

import Typist from 'react-typist';

export default function InfTypist({words}) {
  const [index, setIndex] = useState(0);

  let word = words[index%words.length];
  let Infi = ()=>{ return ( <Typist onTypingDone={()=>{setIndex((i)=> i+1)}}>
      {word}
      <Typist.Backspace count={word.length} delay={word.length*100} />
    </Typist>)}
  return (
    <Infi/>
  )
}

https://codesandbox.io/s/react-text-typing-effect-in-infinite-loop-hooks-forked-14bff?file=/src/index.js

aaronwng avatar Apr 20 '21 08:04 aaronwng

No update on this ticket? It has been opened in 2016... Loop typing should be a stapple feature... Any update?

aress31 avatar Feb 21 '22 00:02 aress31