全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2025-07-25_为了实现AI对话的打字效果,我封装一个vue3自定义指令

您的位置:首页 >> 新闻 >> 行业资讯

为了实现AI对话的打字效果,我封装一个vue3自定义指令 前言随着 DeepSeek 的火爆,公司前段时间也接了一个有关 AI 的项目。其中有一个人机交付对话框页面。逻辑也挺简单,通过 websocket 建立长连接。后端调用 AI 接口给前端推消息。 需求产品希望对话的效果能像市面的 AI 产品一样呈现出一个个出的那种「打字效果」! 技术实现1.CSS + 动画// css @keyframes typing { from { width: 0; } to { width: 100%; } } .typing-effect { overflow: hidden; white-space: nowrap; animation: typing 6s steps(50, end); } //html div class="typing-effect" 日照香炉生紫烟,遥看瀑布挂前川。飞流直下三千尺,疑是银河落九天。 /div 效果效果虽然出来了,但是存在一个比较大的弊端:「无法多行呈现」只要文本较多,超过了一行的情况就出现文字展示不全的情况。 2.js + 定时器function typeWriter(elementId, message, speed) { const element = document.getElementById(elementId); let i = 0; // 当前字符索引 const interval = setInterval(() = { if (i message.length) { element.textContent += message.charAt(i); // 逐字符添加文本 i++; } else { clearInterval(interval); // 完成打字后停止间隔调用 } }, speed); // 控制打字速度,例如50毫秒/字符 } // 使用函数 typeWriter('typewriter', '日照香炉生紫烟,遥看瀑布挂前川。飞流直下三千尺,疑是银河落九天。', 100); // 100毫秒/字符速度 效果与上面是一样的,解决了多行展示。但是熟悉 JS 定时器的小伙伴们都知道,定时器做动画有时候会出现卡顿。所以还得优化一下(把定时器替换成动画帧)。 3. 优化后function typeWriter(elementId, message, speed) { const element = document.getElementById(elementId); let i = 0; let startTime = null; function animate(currentTime) { if (!startTime) startTime = currentTime; if (currentTime - startTime = i * speed) { if (i message.length) { element.textContent += message.charAt(i); i++; requestAnimationFrame(animate); } } else { requestAnimationFrame(animate); } } requestAnimationFrame(animate); } // 使用示例: typeWriter('typewriter', '日照香炉生紫烟,遥看瀑布挂前川。飞流直下三千尺,疑是银河落九天。', 100); 经过一系列优化,效果也达到了,但是用 JS 来做的话页面的 dom 频繁的在改变。如果对话比较多对性能感觉不那么友好。有什么办法能既不频繁的对 dom 进行修改又能多行展示呢? 经过一系列思考,还是回到第一步吧! 用 css 的动画~ 但是第一步的弊端已经说过了,就是如果文字是多行就失效了。那此时我们就想到了一个办法,把文字截取变为 2 行呢?或者 3 行?然后每一行的动画执行完再执行下一行呢? 于是乎要写 2 个方法:根据 div 的宽度计算一行能显示多少个文字 function calculateCharactersPerLine(divElement, fontSize, text) { const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); context.font = `${fontSize}px sans-serif`; // 设置字体大小和样式 const metrics = context.measureText(text); const textWidth = metrics.width; const divWidth = divElement.offsetWidth; // 获取div的宽度 // 计算一行可以容纳多少个字符 const charactersPerLine = Math.floor(divWidth / textWidth) * text.length; if (charactersPerLine == 0) { return calculateCharactersPerLine(divElement, fontSize, text.slice(0, -1)) } return charactersPerLine; } calculateCharactersPerLine('id',16,'日照香炉生紫烟,遥看瀑布挂前川。飞流直下三千尺,疑是银河落九天。') 根据 calculateCharactersPerLine 方法的返回值对整个文本进行切片 function splitIntoChunks(str, chunkSize) { const regexPattern = new RegExp(`.{1,${chunkSize}}`, 'g'); return str.match(regexPattern); } 整体思路就是假如 div 宽度是 100px, 那么可能整个文本会显示 3 行,那么切片出来就是 3 段文字,然后把这 3 段文字创建一个 div 包裹,每个 div 身上再定义动画就行了 「直接用 vue3 指令封装一下吧~ 完整代码如下」: export function typewriter(app) { app.directive('typewriter', (el, binding) = { console.log(binding) if (binding.oldValue) return; renderText(el,binding) } function renderText(el, binding) { const arg = binding.arg || 1 const style = document.createElement('style'); style.textContent = ` @keyframes width { 0% { width: 0; } 100% { width: 100%; } } document.head.appendChild(style); const textLen = calculateCharactersPerLine(el, 16, binding.value); const divList = splitIntoChunks(binding.value, textLen) divList.forEach((row, index) = { const oDiv = document.createElement('div'); oDiv.innerText = row oDiv.style.cssText = ` position: relative; overflow: hidden; width: 0; white-space: nowrap; animation: width ${arg}s steps(50) forwards; animation-delay: ${index*arg}s el.appendChild(oDiv) }) } //计算一行的文字字数 function calculateCharactersPerLine(divElement, fontSize, text) { const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); context.font = `${fontSize}px sans-serif`; // 设置字体大小和样式 const metrics = context.measureText(text); const textWidth = metrics.width; const divWidth = divElement.offsetWidth; // 获取div的宽度 // 计算一行可以容纳多少个字符 const charactersPerLine = Math.floor(divWidth / textWidth) * text.length; if (charactersPerLine == 0) { return calculateCharactersPerLine(divElement, fontSize, text.slice(0, -1)) } return charactersPerLine; } //切片 function splitIntoChunks(str, chunkSize) { const regexPattern = new RegExp(`.{1,${chunkSize}}`, 'g'); return str.match(regexPattern); } template div v-typewriter:[2]="`回环(文)诗、剥皮诗、离合诗、宝塔诗、字谜诗、辘轳诗、八音歌诗、藏头诗、打油诗、诙谐诗、集句诗、联句诗、百年诗、嵌字句首诗、绝弦体诗、神智体诗等40多种。这些杂体诗各有特点,虽然均有游戏色彩,但有些则具有一定的思想性和艺术性,所以深受人们的喜爱`"/div /template 效果总结:好多人会误以为把简单的问题在复杂化。其实写代码就是各种思路的碰撞,如果单纯从实现功能的角度出发,用什么方法都行!怎么样兄弟们,如果你们项目中也有这种需求。你会怎么做?欢迎留言讨论~ AI编程资讯AI Coding专区指南:https://aicoding.juejin.cn/aicoding 点击"阅读原文"了解详情~ 阅读原文

上一篇:2017-09-07_精彩回顾 | 2017CTR洞察高峰论坛完美收官 下一篇:2022-12-02_伍迪·艾伦与他毫无意义的人生

TAG标签:

18
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价