携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第12天,点击查看活动详情
目录结构
|- Typist
|- Typist.jsx
|- Cursor.jsx
|- Cursor.css
|- TypistExample.jsx
|- TypistExample.css
HTML 的结构,两个部分
文字显示部分 + 光标
return (
<div className={`Typist ${className}`}>
{innerTree}
<Cursor />
</div>
);
显然,需要从外部传入 className,delayGenerator(页面显示字数的速度)。定义一个数组 linesToType,接收传入的字符串。textLines 显示在页面上的字符串。
import React, { Component } from 'react';
export default class Typist extends Component {
static defaultProps = {
className: '',
delayGenerator: () => 40,
}
constructor(props) {
super(props);
this.linesToType = [];
if (props.children) {
this.linesToType = [props.children];
}
}
state = {
textLines: [''],
}
render() {
const { className } = this.props;
const innerTree = this.state.textLines;
return (
<div className={`Typist ${className}`}>
{innerTree}
<Cursor />
</div>
);
}
}
typeCharacter 函数,使用 Promise,当上一个字符 reslove 的时候,去调用下一个字符的 Promise。
typeCharacter = (character, charIdx, lineIdx) => {
console.log(character, lineIdx)
return new Promise((resolve) => {
const textLines = this.state.textLines.slice();
textLines[lineIdx] += character;
this.setState({ textLines });
const delay = this.delayGeneratorComp();
setTimeout(resolve, delay);
});
}
delayGeneratorComp 函数去调用传入的 delayGenerator 函数,提供字符显示的速度。
delayGeneratorComp = () => {
return this.props.delayGenerator();
}
utils.js
对于的传入数据,使用传入的 iterator 进行处理。
export function eachPromise(arr, iterator, ...extraArgs) {
const promiseReducer = (prev, current, idx) => {
return prev.then(() => iterator(current, idx, ...extraArgs))
}
return Array.from(arr).reduce(promiseReducer, Promise.resolve());
}
typeLine = (line, lineIdx) => {
utils.eachPromise(line, this.typeCharacter, lineIdx)
}
typeAllLines(lines = this.linesToType) {
utils.eachPromise(lines, this.typeLine)
}
最后在 React 的生命周期中调用这个函数
componentDidMount() {
this.typeAllLines();
}
完整代码:
import React, { Component } from 'react';
import Cursor from './Cursor';
import * as utils from './utils';
export default class Typist extends Component {
static defaultProps = {
className: '',
delayGenerator: () => 40,
}
constructor(props) {
super(props);
this.linesToType = [];
if (props.children) {
this.linesToType = [props.children];
}
}
state = {
textLines: [''],
}
componentDidMount() {
this.typeAllLines();
}
delayGeneratorComp = () => {
return this.props.delayGenerator();
}
typeAllLines(lines = this.linesToType) {
utils.eachPromise(lines, this.typeLine)
}
typeLine = (line, lineIdx) => {
utils.eachPromise(line, this.typeCharacter, lineIdx)
}
typeCharacter = (character, charIdx, lineIdx) => {
console.log(character, lineIdx)
return new Promise((resolve) => {
const textLines = this.state.textLines.slice();
textLines[lineIdx] += character;
this.setState({ textLines });
const delay = this.delayGeneratorComp();
setTimeout(resolve, delay);
});
}
render() {
const { className } = this.props;
const innerTree = this.state.textLines;
return (
<div className={`Typist ${className}`}>
{innerTree}
<Cursor />
</div>
);
}
}
Curosr 组件
光标应该区分是否显示,所以应该使用 shouldRender 变量来控制。当 shouldRender === true 的时候,显示光标。
import React, { Component } from 'react';
import './Cursor.css';
export default class Cursor extends Component {
constructor(props) {
super(props);
this._isReRenderingCursor = false;
this.state = {
shouldRender: true,
};
}
componentDidUpdate() {
if (this._isReRenderingCursor) { return; }
this._reRenderCursor();
}
_reRenderCursor() {
this._isReRenderingCursor = true;
this.setState({ shouldRender: false }, () => {
this.setState({ shouldRender: true }, () => {
this._isReRenderingCursor = false;
});
});
}
render() {
if (this.state.shouldRender) {
return (
<span className={`Cursor Cursor--blinking`}>
|
</span>
);
}
return null;
}
}
加上这个组件,来看一下效果。
页面的 CSS
.Typist .Cursor {
display: inline-block;
}
.Typist .Cursor--blinking {
opacity: 1;
animation: blink 1s linear infinite;
}
@keyframes blink {
0% {
opacity: 1;
}
50% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.TypistExample a {
color: inherit;
}
.TypistExample a:hover {
background-color: #22BAD9;
color: white;
text-decoration: none;
}
.TypistExample-header {
margin-top: 5%;
margin-bottom: 15%;
text-align: center;
font-size: 3em;
cursor: pointer;
color: #22BAD9;
}
.TypistExample-message {
color: hotpink;
width: 400px;
margin: auto;
}
.TypistExample-message .flash {
color: #22BAD9;
font-weight: bold;
text-decoration: underline;
animation-name: blinker;
animation-duration: 0.7s;
animation-timing-function: linear;
animation-iteration-count: infinite;
}
@keyframes blinker {
0% {
opacity: 1.0;
}
50% {
opacity: 0.0;
}
100% {
opacity: 1.0;
}
}
一个最基本的打字机效果就实现了。
本文代码来自 react-typist 组件的代码解析