towxml-steam-typewriter

May 15, 2026 · View on GitHub

演示图片 点击查看效果(由于网络问题,gif动图可能不一定能良好地展示,同时视频转的gif后有点卡顿,你直接下载示例在自己电脑上跑更直接)

towxml-steam-typewriter

使用有问题请联系 微信:zxx-wwj-zbl      

towxml源码及基于此的流式打字机源码讲解视频

背景与核心痛点

随着 AI 大模型在微信小程序/跨端场景的普及,基于 SSE/WebSocket 流式输出 Markdown 文本成为 AI 聊天机器人的标配能力,但主流的小程序 Markdown 渲染库 towxml 在大模型流式接口场景下存在致命性能问题:

  • 流式渲染时需全量解析+重复渲染累计文本,解析/渲染开销随文本长度指数级上升;

  • 长文本场景下出现打字速度骤降、设备发热掉帧、复杂节点重复创建,甚至因 setData 频繁触发导致页面卡死、小程序闪退;

  • 多轮对话后性能持续劣化,成为小程序 AI 聊天场景的核心技术瓶颈。

💡 如果这个项目帮你解决了实际问题,麻烦点个 ⭐ Star 支持一下吧! 你的鼓励是我持续优化、迭代新功能的最大动力~

解决方案

针对上述痛点,我基于 towxml 源码进行深度魔改,打造了 towxml-stream-typer 组件,核心突破如下:

核心技术创新:提出「局部渲染连续文本」+「样式布局稳定节点复用」两大核心思想,从根源避免重复解析/渲染——仅解析新增文本片段,对已渲染的稳定节点(如段落、列表、代码块)完全复用,不再重复转化和渲染;

极致性能表现:安卓/iOS 双端实测,40 分钟长文本流式渲染全程速度一致,5 万字 Markdown 仅耗时 4 分 30 秒(仅比理论极限慢 20 秒),20 万字多轮对话跨轮次耗时差 ≤ 2 秒,彻底解决「越聊越卡」问题;

该方案从理论上讲,无论再长的文本,都是从头到尾一样的丝滑

支持说明

目前这个代码支持在原生微信小程序uniapp vue3taro中使用

npm run build 打包后使用,支持微信开发者工具的代码保护

因为底层模块化的差异,uniapp vue2中使用会有问题,但并非不能使用,需要做一些处理。个人认为,现在使用uniapp vue2的应该很少了,所以我也没必要提供uniapp vue2的demo示例,如果你有需要,直接加我微信咨询

使用方式

将项目下载下来,进入towxml目录执行

  • npm install

  • npm run build 执行这一步之前可以去config.js中配置是否将echarts、latex、yuml等等打包上去,默认是没有打包进去,默认包大小309k(极限最小体积)左右,打包完成在towxml目录下生成dist文件夹

  • 将dist文件夹改名为towxml,放到微信小程序代码或者 uniapp 代码的指定文件夹,作为一个微信小程序组件使用,本质上这就是一个原生微信小程序组件

这里给大家写了一个uniapp和一个原生微信小程序的对话聊天使用示例,大家最好下载下来,模仿着写,里面的注释很详细,并有些最佳实践,如加载历史消息、滚动到底部、终止打印、停留页面终止自动滚动到底部、一键滚动到底部

uniapp使用示例

原生微信小程序使用示例

强烈建议大家参考这两个示例的任何一个进行使用,看使用文档可能费劲,还不如直接看示例代码,里面的注释很详细并有些使用说明

详细说明

1. 由于定制了 towxml 的代码,所以 towxml 组件的 prop 参数发生了改变
2. 目前的属性有:
属性名类型作用默认值
idStringtowxml组件实例id,请务必保证这个id的唯一性,因为每个towmxl组件都独有一份全局数据,以id作为key进行标识的,id重复会导致混乱无默认值,需要你必填
speedNumber每多少秒打印一个字符,值越小打印越快,单位是毫秒,默认 6 毫秒打印一个字符6
openTyperBoolean是否开启打字机,默认开启,如果你已经有完整 markdown 文本,想一次性渲染,可以设置为falsetrue
themeString支持dark 和 light 两种,推荐lightlight
3.目前的事件:

finish:当打字机打印完毕,会发送 finish 事件,你可以通过该事件关闭滚动到底部的定时器等善后工作

historyMessageFinish: 每当一条历史消息加载完毕,就会回调这个事件

4.目前你需要使用的全局函数(都从towxml/globalCb.js进行导入)
函数名参数作用
setMdText(id,text) 第一个参数是towxml组件的id属性,第二个参数是最新的总markdown文本设置当前一次问答中最新的总markdwon文本,每次当你流式接口给你推送新的数据了,你就调一下这个函数,这样对应id的towxml组件才能感知到最新的总文本
setStreamFinish(id) towxml组件的id通知本次问答的流式接口已经结束,当本次回答所有markdown文本都已经全部拿到了,就调用这个函数,这样对应id的towxml就知道打印完当前这些文本,本次打印就完成了
stopImmediatelyCb(id) towxml组件的id立即终止本次打印,比如用户想问新的问题不想看这个回答,就可以调用这个函数对当前对应id的正在打字的towmxl实例进行马上终止打字
scrollCb(e) 滚动事件参数你肯定会用scroll-view之类的组件包裹你的问答,请监听onScroll滚动事件,然后调用用这个函数,这样我才知道你滚动了屏幕,帮你做虚拟显示,节约内存,防止随着问答轮次增加而撑爆内存造成发烫闪退
setQueryTowxmlNodeFn其实不是个函数,你要把赋值一个函数给setQueryTowxmlNodeFn.value帮我通过createSelectorQuery查询towxml组件的节点,一边拿到top值,来进行虚拟显示,使用方式参考上面列举的uniapp或者原生微信小程序使用示例

scrollCbsetQueryTowxmlNodeFn 这两个函数说白了就是你如果需要虚拟显示的功能,就要在你的代码里配合我做些工作,这两个都必须配合好,虚拟显示效果才会生效

当你的内容较多时,不断滚动屏幕时出现以下打印,说明虚拟显示生效了,否则没有生效,没有生效不会报错,也能正常使用,但是内存随着使用会累加,微信小程序的内存占用到一定程度,会被微信杀死造成闪退

image

scrollCb:因为我在组件内部是无法判断屏幕是否滚动,所以需要你在对应的scroll事件中回调这个函数,同时把事件参数传进来,我需要通过事件参数来判断滚动方向,已进行优先显示哪些内容

setQueryTowxmlNodeFn: 由于微信小程序的限制,我在一个towxml实例中,是无法通过createSelectorQuery查询到其他towxml实例的对应dom节点,所以我希望你来帮我查,这样一次性查到所有的towmxl实例的top之后,我就可以进行虚拟显示了

具体怎么操作,参考 uniapp使用示例 或者 原生微信小程序使用示例 使用示例,其实很简单的

5.后续代码更新了,你就把这个towxml文件夹下载下来,打包,然后替换你项目中的towmxl文件夹就行了。当然如果添加了某些新功能,也可能会在demo中写一些代码配合调用。

Star History

Star History Chart