html手写一个打印机效果-从最基础到学会

2,316 阅读5分钟

手写一个打印机效果

啥叫打印机效果,话不多说,直接上效果。我们可以自己写入一段文本然后通过html的方式,让它跟打印机一样,一个一个的打印到页面,并且还可以一个一个的删除。在这里我先浅说一下,我们的实现技巧,定时器setTimeout控制时间,然后for循环遍历写入到页面上。

封装的打印js main(str,text)直接传入要写入的数组对象和要写入的元素。 copy.js 下载到本地引入然后调用它就可以了 image.png

代码

先拿到我们要写入的元素,然后设置好我们要写入的内容。

 var text = document.querySelector('.text');
 var str = ['你好 ,我是一名刚入坑不久的大三在校生。', '现在学习都是为了将来的工作。', '希望能够得到大家的鼓励,谢谢!']

基础代码一

首先这里,我们先实现一个只有一段文字的实现效果。实现思路就是通过计时器,控制好时间,每次写入的文字通过str[0].substr(0, k)拿到,需要注意的是,因为是异步任务,回退的时候,我们的时间要设置好,加上写入完的时间1000 + 200 * str[0].length)

  写入
    for (let j = 0; j < str[0].length; j++) {
        // 使用 setTimeout 函数来实现每个字符的延时输出
        setTimeout(() => {
            text.innerHTML = str[0].substr(0, j) // 显示当前字符串的前 j 个字符
        }, 200 * j) // 延迟时间为 200 毫秒乘以 j,即每个字符间隔 200 毫秒
    }

    // 回退
    // 在所有字符输出完成后,等待 1000 毫秒后开始回退
    setTimeout(() => {
        for (let k = str[0].length, i = 0; k >= 0; k--, i++) {
            // 使用 setTimeout 函数来实现每个字符的延时输出
            setTimeout(() => {
                text.innerHTML = str[0].substr(0, k) // 显示当前字符串的前 k 个字符
            }, 200 * i) // 延迟时间为 200 毫秒乘以 i,即每个字符间隔 200 毫秒
        }
    }, 1000 + 200 * str[0].length) // 等待时间为 1000 毫秒加上所有字符输出的延时时间

基础代码二 错误代码

首先这个代码是错误的 为了能让大家更好的看到错误的效果,于是我把这个代码也上传了。大家可以看到,在这里,页面上的文字总是会莫名奇怪的出现删除,根本不是我们想要的。其实我们也只是对上面一个代码进行了一个for循环遍历,却出现了这样的效果。其实这导致的原因就是setTimeout是异步任务,时间没有控制好。即每个字符串的打印和删除都是异步任务,无法保证它们的执行顺序。因此,可能会出现多个字符串的打印和删除任务交错执行的情况,导致效果不符合预期。

 // 即每个字符串的打印和删除都是异步任务,无法保证它们的执行顺序。因此,可能会出现多个字符串的打印和删除任务交错执行的情况,导致效果不符合预期。
    // 整个str 这是一个有问题的代码 因为计算时间太麻烦了 都是异步任务
    // for (let s = 0; s < str.length; s++) {
    //     // 写入
    //     for (let j = 0; j < str[s].length; j++) {
    //         setTimeout(() => {
    //             text.innerHTML = str[s].substr(0, j)
    //         }, 200 * j)
    //     }
    //     // 回退
    //     setTimeout(() => {
    //         for (let k = str[s].length, i = 0; k >= 0; k--, i++) {
    //             setTimeout(() => {
    //                 text.innerHTML = str[s].substr(0, k)
    //             }, 200 * i)

    //         }
    //     }, 1000 + 200 * str[s].length)
    // }

基础代码三

为了解决上面的问题,我们使用了函数封装并且使用了回调函数实现我们想要的效果。我们将打印和删除都封装成一个含有回调函数的函数,为什么要含有回调函数呢?这是为了我们下面对一个字符串打印和删除的函数做封装。打我们打印完一个字符串时,我们才会执行删除。所有我们将删除函数放到打印的回调函数中去执行。然后我们将打印整个字符串数组进行封装,因为我们在删除的里面也有一个回调函数,那么我们可以在这个回调函数里去执行打印下一条字符串,这样就防止了控制时间不准确的问题。

 // 打印字符串
    function printText(str, callback) {
        var i = 0;
        var timer = setInterval(function () {
            text.innerHTML = str.substr(0, i); // 将字符串的前缀赋值给显示文本的元素
            i++;
            if (i > str.length) { // 如果已经打印完整个字符串
                clearInterval(timer); // 停止定时器
                callback && callback(); // 调用回调函数
            }
        }, 200); // 每 200 毫秒打印一个字符
    }

    // 删除字符串
    function deleteText(str, callback) {
        var i = str.length;
        var timer = setInterval(function () {
            text.innerHTML = str.substr(0, i); // 将字符串的前缀赋值给显示文本的元素
            i--;
            if (i < 0) { // 如果已经删除到空字符串
                clearInterval(timer); // 停止定时器
                callback && callback(); // 调用回调函数
            }
        }, 200); // 每 200 毫秒删除一个字符
    }

    // 打印和删除字符串
    function printAndDeleteText(str, callback) {
        printText(str, function () { // 先打印字符串
            setTimeout(function () {
                deleteText(str, callback); // 等待 1 秒后再删除字符串
            }, 1000);
        });
    }

    // 循环遍历字符串数组,依次打印和删除字符串
    function printAndDeleteAllText(strArr) {
        function printAndDeleteNext(i) {
            if (i >= strArr.length) { // 如果已经处理完所有字符串
                printAndDeleteNext(0); // 重新从头开始处理
            } else {
                printAndDeleteText(strArr[i], function () { // 先打印字符串
                    i++;
                    printAndDeleteNext(i); // 递归调用自身,处理下一个字符串
                });
            }
        }
        printAndDeleteNext(0); // 开始处理第一个字符串
    }
    // 开始打印和删除字符串数组中的所有字符串
    printAndDeleteAllText(str)

最优代码

其实我们做了,这么多,最后就是为了解决异步任务。 所以我这里直接采用Promiseasync await解决上面的问题。我们通过Promise解决实现打印和删除的异步任务。我们通过async await封装整个运行函数,解决了定时器异步问题,不用再计算时间,又难有算不出来。

 // 最终版 封装 解决异步任务
    function writeText(t, delay = 200) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                text.innerHTML = t; // 显示当前字符串 t
                resolve(); // Promise 完成
            }, delay) // 延迟 delay 毫秒后执行
        })
    }

    async function main(str) {
        while (true) { // 无限循环
            for (let j = 0; j < str.length; j++) {
                // 写入
                for (let i = 0; i <= str[j].length; i++) {
                    await writeText(str[j].substr(0, i)) // 显示当前字符串的前 i 个字符
                }
                // 回退
                // 回退前先等一秒
                await new Promise((resolve, reject) => {
                    setTimeout(() => {
                        resolve(); // 等待 1000 毫秒后 Promise 完成
                    }, 1000) // 等待 1000 毫秒
                })
                for (let i = str[j].length; i >= 0; i--) {
                    await writeText(str[j].substr(0, i), 200) // 显示当前字符串的前 i 个字符,间隔 200 毫秒
                }
            }
        }
    }
    main(str)

源码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>打印机效果</title>
    <style>
        .container {
            display: flex;
            /* 使用 flex 布局 */
            flex-direction: column;
            /* 垂直布局 */
            align-items: center;
            /* 水平居中 */
            justify-content: center;
            /* 垂直居中 */
            height: 100vh;
            /* 高度占满整个视口 */
        }

        h1 {
            font-size: 3rem;
            /* 字体大小 */
            margin-bottom: 2rem;
            /* 底部间距 */
            text-align: center;
            /* 居中对齐 */
        }

        .text {
            font-size: 2rem;
            /* 字体大小 */
            font-weight: bold;
            /* 字体加粗 */
            text-align: center;
            /* 居中对齐 */
            border-right: 2px solid black;
            /* 添加光标效果 */
            white-space: nowrap;
            /* 不换行 */
            overflow: hidden;
            /* 隐藏超出部分 */
            animation: blink 0.5s step-end infinite;
            /* 添加光标闪烁效果 */
            height: 3rem;
            /* 设置一个固定的高度 */
        }


        @keyframes blink {

            from,
            to {
                border-color: transparent;
                /* 透明边框颜色 */
            }

            50% {
                border-color: black;
                /* 黑色边框颜色 */
            }
        }
    </style>
</head>

<body>
    <div class="container">
        <h1>逐字打印和删除文字效果</h1>
        <p class="text"></p>
    </div>
</body>
<script>
    var text = document.querySelector('.text');
    var str = ['你好 ,我是一名刚入坑不久的大三在校生。', '现在学习都是为了将来的工作。', '希望能够得到大家的鼓励,谢谢!']


    // 写入
    // for (let j = 0; j < str[0].length; j++) {
    //     // 使用 setTimeout 函数来实现每个字符的延时输出
    //     setTimeout(() => {
    //         text.innerHTML = str[0].substr(0, j) // 显示当前字符串的前 j 个字符
    //     }, 200 * j) // 延迟时间为 200 毫秒乘以 j,即每个字符间隔 200 毫秒
    // }

    // // 回退
    // // 在所有字符输出完成后,等待 1000 毫秒后开始回退
    // setTimeout(() => {
    //     for (let k = str[0].length, i = 0; k >= 0; k--, i++) {
    //         // 使用 setTimeout 函数来实现每个字符的延时输出
    //         setTimeout(() => {
    //             text.innerHTML = str[0].substr(0, k) // 显示当前字符串的前 k 个字符
    //         }, 200 * i) // 延迟时间为 200 毫秒乘以 i,即每个字符间隔 200 毫秒
    //     }
    // }, 1000 + 200 * str[0].length) // 等待时间为 1000 毫秒加上所有字符输出的延时时间


    // 即每个字符串的打印和删除都是异步任务,无法保证它们的执行顺序。因此,可能会出现多个字符串的打印和删除任务交错执行的情况,导致效果不符合预期。
    // 整个str 这是一个有问题的代码 因为计算时间太麻烦了 都是异步任务
    // for (let s = 0; s < str.length; s++) {
    //     // 写入
    //     for (let j = 0; j < str[s].length; j++) {
    //         setTimeout(() => {
    //             text.innerHTML = str[s].substr(0, j)
    //         }, 200 * j)
    //     }
    //     // 回退
    //     setTimeout(() => {
    //         for (let k = str[s].length, i = 0; k >= 0; k--, i++) {
    //             setTimeout(() => {
    //                 text.innerHTML = str[s].substr(0, k)
    //             }, 200 * i)

    //         }
    //     }, 1000 + 200 * str[s].length)
    // }


    // 最终版 封装 解决异步任务
    function writeText(t, delay = 200) {
        return new Promise((resolve, reject) => {
            setTimeout(() => {
                text.innerHTML = t; // 显示当前字符串 t
                resolve(); // Promise 完成
            }, delay) // 延迟 delay 毫秒后执行
        })
    }

    async function main(str) {
        while (true) { // 无限循环
            for (let j = 0; j < str.length; j++) {
                // 写入
                for (let i = 0; i <= str[j].length; i++) {
                    await writeText(str[j].substr(0, i)) // 显示当前字符串的前 i 个字符
                }
                // 回退
                // 回退前先等一秒
                await new Promise((resolve, reject) => {
                    setTimeout(() => {
                        resolve(); // 等待 1000 毫秒后 Promise 完成
                    }, 1000) // 等待 1000 毫秒
                })
                for (let i = str[j].length; i >= 0; i--) {
                    await writeText(str[j].substr(0, i), 200) // 显示当前字符串的前 i 个字符,间隔 200 毫秒
                }
            }
        }
    }
    main(str)
</script>

</html>