需求
由于不满上一家公司不思进取的前端技术栈,从上一家公司离职。目前多了不少时间,想着是否可以将之前废弃的个人博客拾起来,就准备重构自己的个人博客。
首先碰到的一个问题是,我想实现一个打字机效果,但是又可以自动退格,打印下一个单词的效果。网上只有打印出来字体的效果,但是没有退格,所以,使用一段时间,自己写了一个。
技术
技术上使用的是原生的 js(ES6)
以及 React
。
代码
废话不多说,直接上代码:
/**
* 一个用来模拟线程睡眠的方法
* @param {Number} time 睡眠时间 - 必须
* @param {Function} callback 回调函数 - 非必须
* @author qianqian
*/
const sleep = (time = 0, callback) => {
return new Promise((resolve, reject) => {
if (!time) {
reject('the sleep time is required!');
}
if (callback) {
setTimeout(() => {
callback();
resolve();
}, time);
} else {
setTimeout(() => {
resolve();
}, time);
}
});
}
/**
* 开始打印字符,可以将 Node 一个个添加进 DOM 树
* @param {Array} insertNodeList 需要插入的 Node (必须)
* @param {Node} insertedNode 需要被插入的 Node (必须)
* @param {Number} time 插入两个字符之间的间隔 (必须)
* @param {Node} insertBeforeNode 需要插入的在什么 Node 之前 (非必须)
* @author qianqian
*/
const printChar = async (insertNodeList = [], insertedNode = document.body, time = 0, insertBeforeNode) => {
if (insertBeforeNode) {
insertNodeBefore(insertNodeList, insertedNode, time, insertBeforeNode);
} else {
insertNodeIn(insertNodeList, insertedNode, time);
}
// 直接插入的情况
async function insertNodeIn(insertNodeList, insertedNode, time) {
for (let i = 0; i < insertNodeList.length; i ++) {
const insertNode = insertNodeList[i];
await sleep(time);
insertedNode.appendChild(insertNode);
}
}
// 需要插入在特定元素之前的情况
async function insertNodeBefore(insertNodeList, insertedNode, time, insertBeforeNode) {
for (let i = 0; i < insertNodeList.length; i ++) {
const insertNode = insertNodeList[i];
await sleep(time);
insertedNode.insertBefore(insertNode, insertBeforeNode);
}
}
}
/**
* 一个一个开始删除字符,模拟打印删除,当然也可以从 DOM 树中一个个删除 Node
* @param {String} className 类名
* @param {Number} time 删除字符时之间的间隔
* @author qianqian
*/
const deleteChar = async (className = '', time = 0) => {
const pList = document.getElementsByClassName(className) || [];
const length = pList.length;
for (let i = length - 1; i >= 0; i --) {
await sleep(time);
if (pList[i]) {
pList[i].remove();
} else {
continue;
}
}
}
export {
sleep,
printChar,
deleteChar,
}
这里面主要使用的技术是利用 setTimeout()
方法来实现打字机的效果。但是有一个问题,如果直接再循环中使用 setTimeout()
函数来创建打印效果,那么最后的结果会是,经过一段时间后,所有的字几乎在同时显示出来。这是由于,setTimeout()
方法是异步的,利用循环进行创建相当于在一瞬间创建数个计时器,并且这些计时器有等待时间,在等待时间结束之后,一起执行。
为了防止这种情况,我使用了 Promise 类以及async await,来创建一个类似于 Java
中线程睡眠的效果。
主要注意的是,不能使用 forEach
循环,因为 forEach
是同步方法。
在 React 中使用
React 需要挂在组件,所以,如果需要使用这些方法来模拟打字机效果,需要在 useEffect()
—— 无状态组件,或者在 componentDidMount()
中使用:
import React, { useEffect, useState } from 'react';
import { printChar, deleteChar, sleep } from '../../../tools/sleep';
import { betweenDelete, betweenPrint, betweenTime } from '../../../constant/common';
import './Paragraph.scss';
export default function Paragraph(props) {
const [content, setContent] = useState(['coder', 'reader']);
useEffect(() =>{
autoShow(content);
}, []);
// 自动一个一个展示代码
const autoShow = async (content = []) => {
const insertedNode = document.getElementsByClassName('paragraph-show')[0];
const beforeNode = document.getElementsByClassName('paragraph-shaking-cursor')[0];
for (let i = 0; i < content.length; i ++) {
const nodeList = [];
const str = content[i];
for (let j = 0; j < str.length; j ++) {
const p = document.createElement('p');
p.classList.add('paragraph-auto-p');
const value = document.createTextNode(str[j]);
p.appendChild(value);
nodeList.push(p);
}
// allTime += str.length * betweenPrint;
printChar(nodeList, insertedNode, betweenPrint, beforeNode);
// allTime += betweenTime;
await sleep(str.length * betweenPrint + betweenTime);
const pList = insertedNode.getElementsByClassName('paragraph-auto-p');
// allTime += pList.length * betweenDelete + betweenTime;
deleteChar('paragraph-auto-p', betweenDelete);
await sleep(pList.length * betweenDelete + betweenTime);
if (i === content.length - 1) {
i = -1;
}
}
}
return (
<div className='paragraph'>
<div className='paragraph-title'>
<p className='title'>I am a</p>
</div>
<div className='paragraph-autoShow'>
{/* { autoShow(content) } */}
<div className='paragraph-show'>
<p className='paragraph-shaking-cursor'></p>
</div>
</div>
</div>
)
}
问题
目前已知的问题:
由于使用了异步方式,当页面来回切换,可能出现打印顺序乱掉的情况。
并不是 react 内部组件,可能没有办法利用 React 的生命周期,无法正常的卸载。