ai生成的打字机效果

0 阅读5分钟

前言

自从我被换组后,工作也比之前清闲了不少,无非就是普通的业务逻辑,给的时间也没有很短;管理的前端项目的代码的质量也要好上不少,之前的项目完全就是史山。想想之前那个组的组长要我两天把首页+登录+注册+接口+交互效果全部写完,还需要我写适配,要有自己的想法,之前过的日子太苦了。因为我是实习生所以我觉得苦。

接口调用发现有问题,调不通,但是快下班了,就第二天来弄。好好好,一大早就被m怎么不早点跟后端说,问我怎么办,后端请假回家就一直没回来我能怎么办?还要我两天把接口都调完,你咋不上天呢?

算了,不讲废话。直奔主题,最讨厌画大饼,反正我不吃这一套。

前前言

有了前沿,我开始在做完工作后思考该怎么去发展自身,赚更多的马内。希望有大佬指条明路,上班是真的难上。 闲下来一下后,我就想起来之前渡一前端讲的打字机效果怎么做,现在也忘记在哪个视频。

以豆包为例,ai生成的内容在最后面一直有一个光标跟随,美其名曰“打字机效果”,

  • 这个效果是用来提示用户内容还在加载生成,减少用户等待过程中的焦虑。
  • 模拟真实人类沟通输入文字的效果。

但是我还是想吐槽豆包生成内容能不能流畅一点,就跟我检查网站,拖动被塞满请求的窗口样的,一卡一卡的。不否认是我电脑的问题。

技术思路

文字的来源是后端返回的内容,所以会一点一点出现,我们要做的就是如何控制加载效果一直跟随在文字后面,有同学可能会想到使用定位,使用伪元素,但是这样做还是有一个缺陷,怎么跟随在最后一个文字的后方,如果选择跟随在容器的最后面,那当容器变宽变高的话,加载效果会一直在右下,无法跟随文字。

所以这里实现靠两步:

  • 使用js+定时器将文字添加到DOM,实现打字效果。
  • 用css动画实时跟随当前渲染位置,形成视觉引导。

代码

 <div class="chat-container">
        <h3>豆包聊天界面</h3>

        <!-- 消息显示区域 -->
        <div id="message-area" class="conversation-container" style="min-height: 200px;"></div>

        <!-- 输入区域 -->
        <div class="send-area">
            <input type="text" id="input-message" placeholder="输入消息...">
            <button id="send-button">发送</button>
        </div>
    </div>
<script>
        const messageArea = document.getElementById('message-area');
        const inputMessage = document.getElementById('input-message');
        const sendButton = document.getElementById('send-button');

        // 豆包的回答示例
        const doubaoReplies = [
            "你好呀!我是豆包,很高兴和你聊天~有什么想了解的都可以问我哦~",
            "这个问题很有意思呢!让我想想该怎么回答...首先,我们需要从几个方面来分析...",
            "哈哈,你的想法很有创意~关于这个话题,我可以给你分享一些相关的知识和案例~",
            "不用担心,这个问题不难解决~我来告诉你几个简单的方法,你可以试试看~"
        ];

        // 发送按钮点击事件
        sendButton.addEventListener('click', sendMessage);
        inputMessage.addEventListener('keydown', (e) => {
            if (e.key === 'Enter') sendMessage();
        });

        // 发送用户消息并获取豆包回答
        function sendMessage() {
            const userMessage = inputMessage.value.trim();
            if (!userMessage) return;

            // 添加用户消息
            addMessage(userMessage, 'user');
            inputMessage.value = '';

            // 模拟网络请求延迟,获取豆包回答
            setTimeout(() => {
                const reply = doubaoReplies[Math.floor(Math.random() * doubaoReplies.length)];
                showTypingEffect(reply);
            }, 500);
        }

        // 添加消息到聊天区域
        function addMessage(text, type) {
            const messageEl = document.createElement('div');
            messageEl.className = `message ${type === 'user' ? 'user-message' : 'doubao-message'}`;
            messageEl.innerHTML = text;
            messageArea.appendChild(messageEl);
            scrollToBottom();
        }

        // 显示打字机效果和加载符
        function showTypingEffect(text) {
            // 创建豆包消息容器
            const doubaoMessage = document.createElement('div');
            doubaoMessage.className = 'message doubao-message';
            messageArea.appendChild(doubaoMessage);
            scrollToBottom();

            const textElement = document.createElement('span');
            doubaoMessage.appendChild(textElement);

            // 创建加载效果
            const loader = document.createElement('span');
            loader.className = 'loader';
            doubaoMessage.appendChild(loader);

            let index = 0;
            const speed = 100; // 打字速度(毫秒/字符)
            const timer = setInterval(() => {
                // 添加字符到文本
                if (index < text.length) {
                    textElement.textContent += text.charAt(index);
                    index++;
                } else {
                    // 所有字符显示完毕,移除加载效果
                    clearInterval(timer);
                    doubaoMessage.removeChild(loader);
                }
            }, speed);
        }

        // 滚动到聊天区域底部
        function scrollToBottom() {
            messageArea.scrollTop = messageArea.scrollHeight;
        }

        // 页面加载后自动显示一个豆包消息作为示例
        window.addEventListener('load', () => {
            setTimeout(() => {
                showTypingEffect("欢迎使用豆包~我可以回答你的各种问题,比如技术、生活、知识等等,快来和我聊天吧~");
            }, 500);
        });
    </script>
<style>
        /* 聊天容器样式 */
        .chat-container {
            max-width: 600px;
            margin: 30px auto;
            font-family: "Microsoft YaHei", sans-serif;
        }

        /* 消息对聊容器样式 */
        .conversation-container {
            display: flex;
            flex-direction: column;
            gap: 15px;
        }

        /* 消息气泡样式 */
        .message {
            border-radius: 16px;
            padding: 12px 16px;
            margin-bottom: 15px;
            position: relative;
            width: fit-content;
            max-width: 80%;
        }

        .user-message {
            background-color: #f0f9ff;
            margin-left: auto;
            text-align: right;
        }

        .doubao-message {
            background-color: #e6f7ff;
            margin-right: auto;
            text-align: left;
        }

        /* 加载效果样式(光标) */
        .loader {
            display: inline-block;
            width: 8px;
            height: 16px;
            background-color: #409EFF;
            border-radius: 4px;
            margin-left: 4px;
            animation: blink 1.2s infinite;
        }

        /* 光标闪烁动画 */
        @keyframes blink {

            0%,
            100% {
                opacity: 1;
            }

            50% {
                opacity: 0;
            }
        }

        /* 发送按钮样式 */
        .send-area {
            display: flex;
            margin-top: 20px;
        }

        .send-area input {
            flex: 1;
            padding: 10px 14px;
            border: 1px solid #d9d9d9;
            border-radius: 20px 0 0 20px;
            outline: none;
        }

        .send-area button {
            padding: 0 20px;
            background-color: #409EFF;
            color: white;
            border: none;
            border-radius: 0 20px 20px 0;
            cursor: pointer;
        }
    </style>

这里写上一点小优化:当文字内容过长,把时间缩短。

const speed = Math.max(50, 150 - text.length * 2); // 长文本自动加速

解析带有html标签的文本内容:

function parseHtml(html) {
    const temp = document.createElement('div');
    temp.innerHTML = html;
    return Array.from(temp.childNodes);
}

function showRichTextEffect(htmlText) {
    const nodes = parseHtml(htmlText);
    let index = 0;
    setInterval(() => {
        if (index < nodes.length) {
            message.appendChild(nodes[index].cloneNode(true)); 
            index++; 
        } 
     }, 150); 
}

最后,这其实是一篇技术分享文章,其余的内容是进行铺垫,讲述为什么我能发这篇文章。