天天看點

打字機打字,倒退效果

需求

由于不滿上一家公司不思進取的前端技術棧,從上一家公司離職。目前多了不少時間,想着是否可以将之前廢棄的個人部落格拾起來,就準備重構自己的個人部落格。

首先碰到的一個問題是,我想實作一個打字機效果,但是又可以自動倒退,列印下一個單詞的效果。網上隻有列印出來字型的效果,但是沒有倒退,是以,使用一段時間,自己寫了一個。

技術

技術上使用的是原生的

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 的生命周期,無法正常的解除安裝。