One of the dream idea to build typewriting in ReactJS. The code seems to be working, still there are something looks not that great in the code 😔
Looking for better solution (best practice) in the function like animationManager()
Demo: https://codesandbox.io/s/qk4591q1kw
index.js
import React from "react";
import ReactDOM from "react-dom";
import Typewriter from "./Typewriter";
import "./styles.css";
const words = [
"Twinkle twinkle little star",
"How I wonder what you are.",
"Up above the world so high",
"Like a diamond in the sky"
];
function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<Typewriter data={words} />
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Typewriter.js
import React, { Component } from "react";
import PropTypes from "prop-types";
import styled, { keyframes } from "styled-components";
let lastTime = performance.now();
const rotate = keyframes`
0% {
border-right: 2px solid tomato;
}
50% {
border-right: 2px solid transparent;
}
100% {
border-right: 2px solid tomato;
}
`;
const Rotate = styled.span`
color: tomato;
animation: ${rotate} 0.7s linear infinite;
`;
class Typewriter extends Component {
state = {
output: ""
};
index = 0;
rafRef = null;
timeoutRef = null;
static defaultProps = {
typingSpeed: 50,
// it needs to implement in delete function
deletingSpeed: 32,
pauseBeforeRestarting: 200,
pauseBeforeDeleting: 1500,
data: [],
style: {},
className: null
};
componentDidMount() {
this.animationManager();
}
componentWillUnmount() {
// cleanup
if (this.timeoutRef) {
clearTimeout(this.timeoutRef);
}
if (this.rafRef) {
cancelAnimationFrame(this.rafRef);
}
}
animationManager = () => {
this.rafRef = requestAnimationFrame(time => {
const typingData = this.props.data;
this.typeEffect(time, typingData[this.index], () => {
this.timeoutRef = setTimeout(() => {
this.rafRef = requestAnimationFrame(time => {
this.deleteEffect(time, () => {
this.timeoutRef = setTimeout(() => {
this.index =
this.index === typingData.length - 1 ? 0 : this.index + 1;
this.animationManager();
}, this.props.pauseBeforeRestarting);
});
});
}, this.props.pauseBeforeDeleting);
});
});
};
typeEffect = (time, text, callback) => {
if (time - lastTime < this.props.typingSpeed) {
this.rafRef = requestAnimationFrame(time => {
this.typeEffect(time, text, callback);
});
return;
}
lastTime = time;
this.setState({
output: text.substr(0, this.state.output.length + 1)
});
if (this.state.output.length < text.length) {
this.rafRef = requestAnimationFrame(time => {
this.typeEffect(time, text, callback);
});
} else {
return callback();
}
};
deleteEffect = (time, callback) => {
if (time - lastTime < this.props.typingSpeed) {
this.rafRef = requestAnimationFrame(time => {
this.deleteEffect(time, callback);
});
return;
}
lastTime = time;
this.setState({
output: this.state.output.substr(0, this.state.output.length - 1)
});
if (this.state.output.length !== 0) {
this.rafRef = requestAnimationFrame(time => {
this.deleteEffect(time, callback);
});
} else {
return callback();
}
};
render() {
return (
<Rotate className={this.props.className} style={this.props.style}>
{this.state.output}
</Rotate>
);
}
}
Typewriter.propTypes = {
typingSpeed: PropTypes.numnber,
deletingSpeed: PropTypes.numnber,
pauseBeforeRestarting: PropTypes.numnber,
pauseBeforeDeleting: PropTypes.numnber,
data: PropTypes.array,
style: PropTypes.object,
className: PropTypes.string
};
export default Typewriter;
setTimeout- this will add one letter to the string and then check if there are letters left. If so, call the samesetTimeoutagain. I think you have too many intervals and requestanimationframes going on. \$\endgroup\$