尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

TaoToken 实战:uniapp 光标跟随与打字机效果配置指南

发布时间:2026/9/29 6:34:11

资讯中心
01
ARTICLE

TaoToken 实战:uniapp 光标跟随与打字机效果配置指南

TaoToken 实战:uniapp 光标跟随与打字机效果配置指南
1. uniapp 聊天输入场景里光标跟随和打字机为什么总打架做 uniapp 聊天或客服输入框时很多人会碰到一个很别扭的现象文字一个字一个字往外蹦但那个代表“正在输入”的光标要么停在原地不动要么直接飘到屏幕左上角甚至干脆消失。你明明写了打字机定时器也写了光标元素可两者就是不同步。这个问题的本质是打字机效果控制的是文本内容的增量渲染而光标跟随控制的是光标元素相对文本末尾的坐标。文本每增加一个字末尾位置就变了但光标元素的位置是上一次计算出来的没有跟着刷新于是视觉上就脱节了。适合谁看正在用 uniapp 做 AI 对话、客服自动回复、流式输出展示的前端同学。你需要对 Vue 的 ref、DOM 节点遍历、Range API 有基本了解但不需要很深的底层功底。我试过把光标写成一个绝对定位的小圆点用transform: translate(x, y)去追文本末尾。核心难点在于怎么在每次文本更新后准确拿到“最后一个有内容的文本节点”的位置。下面把可复制的配置、定时器骨架和 settings.json 关键项都拆开讲最后给真机验证步骤。2. TaoToken 前置把流式输出接进来打字机效果的数据源通常来自大模型接口。如果你还在用本地 mock 字符串可以先接一个真实可用的流式接口这样光标跟随才有实际意义。TaoToken 提供兼容 OpenAI 风格的接口模型对话、编码计划、控制台和密钥管理都有独立入口。接入前先在控制台创建 API Key然后按文档拼请求。官网入口https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_contentAPI 基地址https://taotoken.net/api模型对话页https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite控制台https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewriteAPI Keyshttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewriteCoding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite注意API Key 只放在服务端或本地开发环境不要硬编码进前端仓库。uniapp 打包成 App 后前端代码是可被反编译的。拿到 Key 后请求体大致长这样stream: true是流式输出的开关{ model: gpt-4o-mini, stream: true, messages: [ { role: system, content: 你是一个客服助手 }, { role: user, content: 帮我查一下订单状态 } ] }流式返回的每一段是 SSE 格式前端用fetch的ReadableStream逐块读取拼出增量文本再喂给打字机逻辑。这样光标跟随才有真实的“逐字推进”场景。3. 可复制配置input 光标定位 逐字输出定时器骨架3.1 模板结构容器、文本、光标三层光标要跟随必须有一个相对定位的容器作为坐标系原点。文本渲染组件放在容器内光标用绝对定位加transform移动。template view classcontainer refcontentRef u-parse :contentnodeText reftextRef/u-parse view classcursor v-showcursorShow :style{ transform: translate(${x}px, ${y}px) } /view /view /template这里contentRef是坐标系容器textRef是富文本渲染组件。光标用v-show控制显隐位置靠x、y两个响应式变量驱动。3.2 样式光标闪烁动画光标样式可以自定义重点是闪烁动画和定位基准。容器必须position: relative否则光标的绝对定位会跑到页面根节点。style scoped langscss .container { position: relative; width: 100%; height: 100vh; box-sizing: border-box; padding: 30rpx 20rpx; .cursor { position: absolute; left: 10rpx; top: 10rpx; width: 30rpx; height: 30rpx; background-color: #000; border-radius: 50%; animation: cursorAnimate 0.5s infinite; } keyframes cursorAnimate { 0% { opacity: 0; } 50% { opacity: 1; } 100% { opacity: 0; } } } /style3.3 逐字输出定时器骨架打字机的核心是“按索引切片 延时”。不要用setInterval直接改文本因为流式数据到达节奏不均匀容易和光标刷新错位。用async/await加delay更可控。methods: { delay(time) { return new Promise((resolve) setTimeout(resolve, time)); }, async typewriter(fullText) { this.cursorShow true; for (let i 0; i fullText.length; i) { this.nodeText fullText.slice(0, i); this.updateCursor(); await this.delay(80); } this.cursorShow false; } }i fullText.length是为了让最后一个字符也能被渲染出来。每次更新文本后立刻调用updateCursor()保证光标和文本末尾同步。3.4 光标定位Range API 找末尾坐标这是整个方案最关键的一步。思路是在最后一个文本节点后面插入一个零宽字符的临时节点用Range拿到它的坐标再减去容器的坐标得到相对偏移。updateCursor() { const lastTextNode this.getLastTextNode(this.$refs.textRef, 1); const tempText document.createTextNode(\u200B); if (lastTextNode) { lastTextNode.parentNode lastTextNode.parentNode.appendChild(tempText); } else { this.$refs.textRef this.$refs.textRef.$el.appendChild(tempText); } const range document.createRange(); range.setStart(tempText, 0); range.setEnd(tempText, 0); const rect range.getBoundingClientRect(); const textRect this.$refs.contentRef this.$refs.contentRef.$el.getBoundingClientRect(); if (textRect) { this.x rect.left - textRect.left 10; this.y rect.top - textRect.top; } tempText.remove(); }getLastTextNode负责深度遍历跳过空文本节点找到最后一个有内容的文本节点getLastTextNode(node, index 1) { if (index 1) { node node.$el; } if (!node) return null; if (node.nodeType Node.TEXT_NODE node.textContent?.trim()) { return node; } for (let i node.childNodes.length - 1; i 0; i--) { const childNode node.childNodes[i]; const textNode this.getLastTextNode(childNode, index 1); if (textNode) return textNode; } return null; }3.5 settings.json 关键项在 uniapp 项目里pages.json或manifest.json的配置会影响 DOM 渲染和真机表现。如果你用的是 H5 端pages.json里对应页面的style建议加上{ path: pages/chat/chat, style: { navigationBarTitleText: 客服对话, app-plus: { softinputMode: adjustResize } } }adjustResize让软键盘弹出时页面重新布局避免光标坐标基于旧视口计算而偏移。App 端还要在manifest.json里确认webview渲染模式V3 编译器下 DOM 操作更接近标准浏览器行为。4. 验证请求真机跑通流式输出与光标同步4.1 先验证接口能流式返回在接入打字机之前先用一个最小请求确认流式通道是通的。可以用 Node 脚本或 Postman 发请求观察是否逐块返回。async function streamChat() { const res await fetch(https://taotoken.net/api/v1/chat/completions, { method: POST, headers: { Content-Type: application/json, Authorization: Bearer YOUR_API_KEY }, body: JSON.stringify({ model: gpt-4o-mini, stream: true, messages: [{ role: user, content: 用一句话介绍uniapp }] }) }); const reader res.body.getReader(); const decoder new TextDecoder(); let buffer ; while (true) { const { done, value } await reader.read(); if (done) break; buffer decoder.decode(value, { stream: true }); const lines buffer.split(\n); buffer lines.pop(); for (const line of lines) { if (line.startsWith(data: ) line ! data: [DONE]) { const json JSON.parse(line.slice(6)); const delta json.choices[0]?.delta?.content || ; process.stdout.write(delta); } } } }跑通后你会看到文字一段一段打印出来说明流式通道正常。4.2 真机验证光标跟随把上面的typewriter和updateCursor接进页面在 H5 和 App 真机分别验证。重点观察三个点第一文本每增加一个字光标是否立刻右移或换行到下一行行首。第二文本换行时光标是否跳到新行末尾而不是留在上一行。第三软键盘弹出后光标是否还在正确位置。如果 H5 正常但 App 偏移大概率是getBoundingClientRect在 webview 里的坐标系和容器不一致可以在updateCursor里打印rect和textRect对比。4.3 成功结果长什么样正常同步时光标会像打字机上的指示器一样始终贴在最后一个字符后面闪烁节奏稳定。文本停止输出后光标隐藏或停在末尾。整个过程中输入框焦点不丢失用户可以直接接着输入。5. 本篇常见错排查5.1 光标停在左上角不动最常见的原因是容器没有position: relative或者contentRef的 ref 拿不到$el。检查模板里refcontentRef是否写在根view上以及updateCursor里是否做了空值判断。另一个原因是getLastTextNode返回了null导致临时节点被追加到textRef.$el上而textRef.$el的坐标系和容器不一致。可以在函数里加日志确认返回的节点。5.2 光标位置偏移一个字符通常是rect.left - textRect.left之后没有加上光标自身宽度的一半或者 padding 没算进去。上面代码里 10是经验值实际要根据你的padding和光标尺寸调整。建议把偏移量抽成变量方便调试。5.3 打字机速度忽快忽慢如果数据源是流式接口不要每收到一个 chunk 就立刻渲染而是把 chunk 追加到一个缓冲区打字机从缓冲区按固定节奏取字。否则网络快时文字瞬间刷完网络慢时又卡住。// 缓冲区方案 this.buffer delta; // 打字机循环从 buffer 里 slice5.4 真机上光标闪烁但文本不更新检查nodeText是否被u-parse正确解析。有些富文本组件对不完整的 HTML 标签容错差切片到一半的p会导致渲染异常。建议先按纯文本渲染验证再换成富文本。5.5 软键盘弹出后光标错位在pages.json里确认softinputMode为adjustResize。如果还是错位可以在键盘弹起事件里延迟 100ms 再调一次updateCursor等布局稳定后重新计算。6. 接入与排障入口如果你在接流式接口时遇到鉴权或返回格式问题先去 API Keys 页面确认 Key 状态再对照接入文档检查请求头和 body 字段。模型对话页可以直接测试模型是否正常响应适合在写代码前先确认通道可用。API Keyshttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite模型对话https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodel_chatutm_campaignrewrite如果你打算长期做编码类 Agent 或批量调用Coding Plan 的额度模型更适合持续开发场景Coding Planhttps://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite光标跟随这件事调通一次之后就是纯配置问题。真正容易翻车的是流式数据的节奏和真机坐标系差异把这两块用日志盯住基本不会再返工。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。