├── .gitignore
├── .babelrc
├── .npmignore
├── webpack.config.js
├── README.md
├── LICENSE
├── package.json
└── src
└── index.js
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | npm-debug.log
3 | yarn-error.log
4 | .DS_Store
5 | lib
6 |
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": ["env", "react"],
3 | "plugins": ["transform-class-properties"]
4 | }
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | src
3 | .babelrc
4 | webpack.config.js
5 | npm-debug.log
6 | yarn-error.log
7 | tests
8 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require("path");
2 |
3 | module.exports = {
4 | entry: "./src/index.js",
5 | output: {
6 | path: path.resolve(__dirname, "lib"),
7 | filename: "index.js",
8 | libraryTarget: "umd"
9 | },
10 | module: {
11 | rules: [
12 | {
13 | test: /\.js$/,
14 | exclude: /node_modules/,
15 | use: "babel-loader"
16 | }
17 | ]
18 | },
19 | externals: {
20 | react: "umd react",
21 | "react-dom": "umd react-dom"
22 | }
23 | };
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Typing Effect in React
2 |
3 | Efficient & Lightweight typing effect made using requestAnimationFrame
4 |
5 | ### Example
6 |
7 |
8 |
9 | ### Installation
10 |
11 | ```sh
12 | npm install --save typing-effect-react
13 | ```
14 |
15 | ### Basic Usage
16 |
17 | ```js
18 | import React, { Component } from "react";
19 | import TypingEffect from "typing-effect-react";
20 |
21 | class YourComponent extends Component {
22 | render() {
23 | return ;
24 | }
25 | }
26 | ```
27 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "typing-effect-react",
3 | "version": "1.1.0",
4 | "description": "Efficient & Lightweight typing effect",
5 | "main": "lib/index.js",
6 | "scripts": {
7 | "test": "jest",
8 | "test:watch": "jest --watch",
9 | "build": "webpack"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git+https://github.com/linasmnew/typing-effect-react.git"
14 | },
15 | "author": "Linas Mickevicius ",
16 | "license": "MIT",
17 | "bugs": {
18 | "url": "https://github.com/linasmnew/typing-effect-react/issues"
19 | },
20 | "homepage": "https://github.com/linasmnew/typing-effect-react#readme",
21 | "devDependencies": {
22 | "babel-core": "^6.26.0",
23 | "babel-loader": "^7.1.2",
24 | "babel-plugin-transform-class-properties": "^6.24.1",
25 | "babel-preset-env": "^1.6.1",
26 | "babel-preset-react": "^6.24.1",
27 | "jest": "^22.1.4",
28 | "prettier": "1.10.2",
29 | "react": "^16.2.0",
30 | "react-dom": "^16.2.0",
31 | "webpack": "^3.10.0"
32 | },
33 | "peerDependencies": {
34 | "react": ">=15.0.0",
35 | "react-dom": ">=15.0.0"
36 | },
37 | "keywords": ["typing-effect", "react", "react-component"]
38 | }
39 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 |
3 | let lastTime = performance.now();
4 |
5 | const myCSS = `
6 | @keyframes typing-effect-react {
7 | 0% {
8 | opacity: 0;
9 | }
10 | 50% {
11 | opacity: 1;
12 | }
13 | 100% {
14 | opacity: 0;
15 | }
16 | }
17 |
18 | @-moz-keyframes typing-effect-react {
19 | 0% {
20 | opacity: 0;
21 | }
22 | 50% {
23 | opacity: 1;
24 | }
25 | 100% {
26 | opacity: 0;
27 | }
28 | }
29 |
30 | @-webkit-keyframes typing-effect-react {
31 | 0% {
32 | opacity: 0;
33 | }
34 | 50% {
35 | opacity: 1;
36 | }
37 | 100% {
38 | opacity: 0;
39 | }
40 | }
41 |
42 | @-ms-keyframes typing-effect-react {
43 | 0% {
44 | opacity: 0;
45 | }
46 | 50% {
47 | opacity: 1;
48 | }
49 | 100% {
50 | opacity: 0;
51 | }
52 | }
53 |
54 | @-o-keyframes typing-effect-react {
55 | 0% {
56 | opacity: 0;
57 | }
58 | 50% {
59 | opacity: 1;
60 | }
61 | 100% {
62 | opacity: 0;
63 | }
64 | }`;
65 |
66 | class TypeEffect extends Component {
67 | state = {
68 | output: ""
69 | };
70 | index = 0;
71 | rafRef = null;
72 | timeoutRef = null;
73 |
74 | componentWillMount() {
75 | const exists = document.head.querySelectorAll("[data-typing-effect-react]");
76 |
77 | if (!exists.length) {
78 | const styleElement = document.createElement("style");
79 | styleElement.setAttribute("data-typing-effect-react", "true");
80 | document.head.appendChild(styleElement);
81 | styleElement.textContent = myCSS;
82 | }
83 | }
84 |
85 | componentDidMount() {
86 | // kick the animation off
87 | this.animationManager();
88 | }
89 |
90 | componentWillUnmount() {
91 | // cleanup
92 | if (this.timeoutRef) clearTimeout(this.timeoutRef);
93 | if (this.rafRef) cancelAnimationFrame(this.rafRef);
94 | }
95 |
96 | animationManager = () => {
97 | // register the very first rAF which will start the animation off
98 | this.rafRef = requestAnimationFrame(time => {
99 | // start the typing animation to animate the currently indexed word
100 | this.typeEffect(time, this.props.data[this.index], () => {
101 | // typing animation finished, time to start removal animation
102 | // but first make a pause
103 | this.timeoutRef = setTimeout(() => {
104 | this.rafRef = requestAnimationFrame(time => {
105 | this.deleteEffect(time, () => {
106 | // removal animation finished, which completes the current animation cycle...
107 | // so make a pause here & start the next animation cycle
108 | this.timeoutRef = setTimeout(() => {
109 | // if on last element, then go back to the first one, else go to the next one
110 | this.index =
111 | this.index === this.props.data.length - 1
112 | ? 0
113 | : this.index + 1;
114 | // start again...
115 | this.animationManager();
116 | }, this.props.pauseBeforeRestarting);
117 | });
118 | });
119 | }, this.props.pauseBeforeDeleting);
120 | });
121 | });
122 | };
123 |
124 | typeEffect = (time, text, callback) => {
125 | // skip animation if the desired number of ms haven't elapsed
126 | if (time - lastTime < this.props.typingSpeed) {
127 | this.rafRef = requestAnimationFrame(time => {
128 | this.typeEffect(time, text, callback);
129 | });
130 | return;
131 | }
132 | lastTime = time;
133 | // add the next character to output
134 | this.setState({
135 | output: text.substr(0, this.state.output.length + 1)
136 | });
137 | // if more characters left to type out,
138 | // then register a call to yourself again to take care of the next character, and so on...
139 | if (this.state.output.length < text.length) {
140 | this.rafRef = requestAnimationFrame(time => {
141 | this.typeEffect(time, text, callback);
142 | });
143 | } else {
144 | // if no characters left to type, i.e. all been typed out
145 | // then let the caller know
146 | callback();
147 | }
148 | };
149 |
150 | deleteEffect = (time, callback) => {
151 | // skip animation if the desired number of ms haven't elapsed
152 | if (time - lastTime < this.props.typingSpeed) {
153 | this.rafRef = requestAnimationFrame(time => {
154 | this.deleteEffect(time, callback);
155 | });
156 | return;
157 | }
158 | lastTime = time;
159 | // remove the next character from output
160 | this.setState({
161 | output: this.state.output.substr(0, this.state.output.length - 1)
162 | });
163 | // if more characters left to delete,
164 | // then register a call to yourself again to take care of the next character, and so on...
165 | if (this.state.output.length !== 0) {
166 | this.rafRef = requestAnimationFrame(time => {
167 | this.deleteEffect(time, callback);
168 | });
169 | } else {
170 | // if no characters left to delete, i.e. all been deleted
171 | // then let the caller know
172 | callback();
173 | }
174 | };
175 |
176 | render() {
177 | const blinkingStyles = {
178 | WebkitAnimation: "1s typing-effect-react step-end infinite",
179 | MozAnimation: "1s typing-effect-react step-end infinite",
180 | MsAnimation: "1s typing-effect-react step-end infinite",
181 | OAnimation: "1s typing-effect-react step-end infinite",
182 | animation: "1s typing-effect-react step-end infinite"
183 | };
184 |
185 | return (
186 |
187 | {this.state.output}
188 | |
189 |
190 | );
191 | }
192 | }
193 |
194 | TypeEffect.defaultProps = {
195 | typingSpeed: 32, // frame delay period between each character
196 | deletingSpeed: 32, // frame delay period between each character
197 | pauseBeforeRestarting: 1000,
198 | pauseBeforeDeleting: 1500,
199 | data: [],
200 | style: {},
201 | className: null
202 | };
203 |
204 | export default TypeEffect;
205 |
--------------------------------------------------------------------------------