1. 底部 input 被键盘顶飞的现场还原
微信小程序里做聊天页或者底部表单页,最容易翻车的地方就是那个贴着屏幕底边的input。页面在浏览器模拟器里看着一切正常,点一下输入框,光标闪得挺欢;可一旦上真机,软键盘"唰"地弹起来,输入框要么被整个盖住,要么只露出半截,用户根本看不见自己打了什么字。这个问题的核心检索词就是微信小程序 input 被键盘盖住,而解决它的关键参数就是cursor-spacing和adjust-position。
先说清楚这两个属性到底管什么。adjust-position是布尔值,默认true,意思是"键盘弹起时,页面自动往上推,保证输入框可见"。cursor-spacing是数字,单位 px,指定光标和键盘顶部之间要留多少距离。很多人以为设了adjust-position="true"就万事大吉,结果发现页面是推上去了,但输入框紧贴着键盘边缘,视觉上还是"糊"在一起,体验很差。这时候就需要cursor-spacing来补一刀,把间距撑开。
这个场景适合谁?适合所有在做聊天、评论、客服、下单备注这类"底部固定输入栏"的开发者。尤其是用position: fixed; bottom: 0布局的同学,几乎百分百会踩这个坑。因为 fixed 定位的元素不参与页面文档流,键盘弹起时系统推的是页面滚动,fixed 元素的行为在不同机型上表现不一致,iOS 和 Android 的差异尤其明显。
我试过在一个聊天页里,输入框用 fixed 固定在底部,adjust-position默认开着,iOS 上表现还行,但 Android 某些机型上输入框直接被键盘盖住,用户得手动往上滑才能看到。后来把cursor-spacing加上,再配合键盘高度监听动态调整,才彻底稳住。下面我把整个排查和配置过程拆开讲,你可以直接照着改。
在动手之前,先明确一个排查顺序:先看adjust-position有没有被误关,再看cursor-spacing设了没,最后看需不需要监听键盘高度做动态补偿。这三步基本能覆盖 90% 的遮挡问题。接下来我会先讲怎么准备调试环境,再给可复制的配置片段,然后上真机验证。
2. 用 TaoToken 准备调试与联调环境
在正式改代码之前,我习惯先把调试和联调的链路搭好。因为小程序开发经常需要一边看真机表现,一边对照接口返回、日志和模型输出,尤其是当你的输入框还要对接后端接口或者 AI 对话能力时,一个稳定的调试入口能省很多事。这里我用 TaoToken 来做接口联调和模型对话验证,它的 API 入口是https://taotoken.net/api,官网在https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content=。
为什么调试输入框遮挡还要扯到接口平台?因为实际项目里,输入框提交后往往要调后端。如果后端接口本身不稳定,你会分不清"输入框被盖住"和"请求失败"哪个才是真问题。把接口链路先跑通,排障时变量就少一个。TaoToken 在这里的角色是提供一个统一的模型调用入口,你可以用它来验证输入内容提交后的处理逻辑,比如把用户输入发给模型做意图识别,看返回是否正常。
具体操作上,先去控制台创建一个 API Key。打开https://taotoken.net/console,登录后在 API Keys 页面新建一个密钥,复制保存好。这个 Key 后面会用在请求头里。注意不要把它硬编码到小程序前端代码里,小程序的前端代码是可以被反编译的,Key 泄露风险很高。正确做法是把 Key 放在你自己的后端服务里,小程序只调你的后端,后端再转发到 TaoToken。
如果你只是想快速验证模型对话效果,可以直接用模型对话页面https://taotoken.net/model-chat,在里面输入测试文本,看返回是否符合预期。这个页面适合调 prompt 和验证模型能力,不需要写代码。等你确认模型输出没问题,再把它接到后端接口里。
对于需要长期做编码和 Agent 开发的场景,可以了解下 Coding Plan,入口在https://taotoken.net/coding-plan。它适合那种需要反复调用模型、做代码生成或者自动化任务的开发者。不过对于本文的输入框遮挡问题,你其实用不到这么重的配置,一个 API Key 加一个模型对话验证就够了。
接入文档在https://taotoken.net/doc,里面有完整的请求示例和参数说明。我建议你在改小程序代码之前,先花十分钟把文档里的快速开始跑一遍,确认你的网络环境能正常访问 API。这一步看起来和输入框无关,但它能帮你排除"到底是前端布局问题还是后端请求问题"的干扰。
环境准备好之后,我们进入正题。下面先给可复制的配置片段,包括page.json和input属性,然后讲每个参数为什么这么设。
3. 可复制的 page.json 与 input 配置片段
先看页面结构。假设你有一个聊天页pages/chat/chat,底部是固定输入栏。page.json里主要控制页面样式和导航栏,真正影响键盘行为的是input组件本身的属性。不过page.json里可以配置"disableScroll"之类的选项来辅助控制滚动行为,所以我一并给出。
page.json配置如下:
{ "navigationBarTitleText": "聊天", "disableScroll": false, "usingComponents": {} }这里disableScroll设为false,允许页面滚动。有些同学为了防穿透把它设成true,结果键盘弹起时页面无法滚动,输入框反而更容易被盖住。除非你有明确的防穿透需求,否则保持false。
接下来是核心的input配置。在chat.wxml里:
<view class="input-bar"> <input class="msg-input" type="text" value="{{inputValue}}" placeholder="请输入内容" confirm-type="send" adjust-position="{{true}}" cursor-spacing="20" bindinput="onInput" bindconfirm="onSend" bindfocus="onFocus" bindblur="onBlur" /> </view>对应的chat.wxss:
.input-bar { position: fixed; left: 0; right: 0; bottom: 0; padding: 16rpx 24rpx; background: #ffffff; box-shadow: 0 -2rpx 12rpx rgba(0, 0, 0, 0.06); z-index: 100; } .msg-input { height: 72rpx; line-height: 72rpx; padding: 0 24rpx; background: #f5f5f5; border-radius: 36rpx; font-size: 28rpx; }关键参数说明,用表格对照更清楚:
| 属性 | 取值 | 作用 | 建议 |
|---|---|---|---|
adjust-position | true/false | 键盘弹起时是否自动上推页面 | 保持true,除非自己接管 |
cursor-spacing | 数字,单位 px | 光标与键盘顶部的距离 | 设 20 到 100,视 UI 而定 |
confirm-type | send/done等 | 键盘右下角按钮文案 | 聊天页用send |
hold-keyboard | true/false | 点击页面是否保持键盘 | 默认false即可 |
cursor-spacing的取值逻辑要理解清楚:系统会取"input 距离页面底部的距离"和"cursor-spacing指定值"两者中的最小值,作为光标与键盘的距离。也就是说,如果你的 input 本身离底部很近,cursor-spacing设再大也没用,因为取的是最小值。这就是为什么光设cursor-spacing有时不生效——input 离底部太近了。
解决办法是给 input 外面包一层有底部内边距的容器,或者用padding-bottom把 input 往上抬一点。比如上面的.input-bar加了padding: 16rpx 24rpx,input 距离屏幕底部就有了 16rpx 的缓冲,cursor-spacing再设 20px,实际间距就会更合理。
如果你需要更精细的控制,比如键盘弹起时动态调整输入栏位置,就需要监听键盘高度。微信小程序提供了wx.onKeyboardHeightChange接口:
Page({ data: { inputValue: '', keyboardHeight: 0 }, onLoad() { this.keyboardHandler = (res) => { this.setData({ keyboardHeight: res.height }); }; wx.onKeyboardHeightChange(this.keyboardHandler); }, onUnload() { wx.offKeyboardHeightChange(this.keyboardHandler); }, onInput(e) { this.setData({ inputValue: e.detail.value }); }, onSend() { const text = this.data.inputValue.trim(); if (!text) return; console.log('发送内容:', text); this.setData({ inputValue: '' }); } });拿到keyboardHeight后,你可以动态设置输入栏的bottom值,让它在键盘上方固定。不过大多数情况下,adjust-position加cursor-spacing已经够用,动态监听是给那些 fixed 布局在 Android 上表现异常的机型兜底的。
配置写完后,别急着上真机,先在开发者工具里点一下输入框,看模拟键盘弹起时页面有没有上推。工具里的表现和真机有差异,但至少能确认属性写对了。接下来进入真机验证环节。
4. 真机验证:iOS 与 Android 间距实测
真机验证是这一步的重头戏,因为 iOS 和 Android 对键盘的处理机制完全不同。iOS 的键盘弹起是系统级的,页面推举比较平滑;Android 则因厂商定制差异很大,有的推页面,有的直接覆盖,还有的会改变窗口高度。所以同一套配置,两个平台表现可能天差地别。
验证步骤我分成四步,你可以照着做。
第一步,准备两台设备,一台 iOS,一台 Android。iOS 建议用较新系统版本,Android 尽量覆盖一个原生系统和一个定制系统(比如小米、华为各一台)。如果手头设备有限,至少保证两个平台各测一台。
第二步,在 iOS 上打开小程序聊天页,点击底部 input。观察三件事:输入框是否可见、光标与键盘顶部之间有没有间距、页面是否被推得过高导致顶部内容消失。正常情况下,adjust-position="true"会让页面整体上推,input 停在键盘上方,cursor-spacing="20"会让光标和键盘之间留出约 20px 的视觉间距。如果输入框紧贴键盘,说明cursor-spacing没生效,检查 input 距离底部的距离是不是小于 20px。
第三步,在 Android 上重复同样操作。Android 上重点看输入框有没有被完全盖住。有些 Android 机型在adjust-position="true"时不会推页面,而是把键盘覆盖在页面上,这时候 input 就被盖住了。解决办法是监听keyboardHeight,动态把输入栏的bottom设为键盘高度:
// 在 onKeyboardHeightChange 回调里 this.setData({ keyboardHeight: res.height, inputBarBottom: res.height });然后在 wxml 里绑定样式:
<view class="input-bar" style="bottom: {{inputBarBottom}}px;">注意,如果你用了动态bottom,就要把adjust-position设为false,否则系统推举和你的动态调整会打架,页面会跳来跳去。
第四步,记录两个平台的实测结果。我实测下来,iOS 上adjust-position="true"加cursor-spacing="20"基本能解决遮挡,输入框稳稳停在键盘上方。Android 原生系统表现接近 iOS,但部分定制系统需要动态监听键盘高度才能彻底消除遮挡。测试时还要注意横竖屏切换,横屏下键盘高度变化更大,遮挡问题更容易出现。
验证通过的标准很简单:点击 input,键盘弹起,输入框完整可见,光标和键盘之间有舒适间距,页面没有异常跳动。三个条件都满足,就算搞定。
如果你在验证过程中发现输入框还是被盖住,别急,下一节我把常见报错和排查方法列出来。
5. 常见报错与排查:从 401 到 local proxy failed
排查输入框遮挡时,你可能会遇到一些看起来不相关的报错,比如接口 401、local proxy failed、reading 'choices'之类的。这些报错和键盘遮挡本身没关系,但它们会干扰你的判断,让你误以为是布局问题。我把常见的几类列出来,对照排查。
第一类,接口 401。这通常是你调 TaoToken API 时 Key 没带对或者过期了。检查请求头里的Authorization字段,格式是Bearer 你的Key。如果你在小程序前端直接调 API,还要注意小程序的request合法域名配置,没配的话请求会被拦截,表现可能是请求失败而不是 401。正确做法是后端转发,前端只调自己的后端。
第二类,local proxy failed。这个报错一般出现在你本地起了代理服务做转发,但代理没启动或者端口不对。排查时先确认代理进程在跑,再确认小程序请求的地址和代理端口一致。如果你用的是 TaoToken 的 API 入口https://taotoken.net/api,确认网络能正常访问,可以用 curl 先测一下:
curl -X POST https://taotoken.net/api/v1/chat/completions \ -H "Authorization: Bearer 你的Key" \ -H "Content-Type: application/json" \ -d '{"model":"你的模型ID","messages":[{"role":"user","content":"test"}]}'返回正常 JSON 说明链路通,返回错误就看错误信息定位。
第三类,reading 'choices'。这是解析响应时choices字段不存在导致的,通常是因为请求失败返回了错误结构,但代码直接去读data.choices[0]。加个判断:
if (res.data && res.data.choices && res.data.choices.length > 0) { const reply = res.data.choices[0].message.content; } else { console.error('响应结构异常:', res.data); }第四类,OAuth 相关报错。如果你用了需要 OAuth 授权的模型服务,token 过期会报这个。重新走一遍授权流程,拿到新 token 再试。
第五类,也是和本文最相关的:输入框配置写了但没生效。排查顺序是:先确认adjust-position没被设成false,再确认cursor-spacing的值和 input 距底部距离的关系,最后确认有没有其他样式(比如position: absolute或父容器overflow: hidden)干扰。有个隐蔽的坑是父容器设了overflow: hidden,键盘弹起时页面推举被裁剪,输入框看起来没动。
如果你在配置里用到了 CC Switch、Cline MCP 或者 Codex 的auth.json,记得三件套要写全:Base URL、Key、Model ID。缺一个都会导致请求失败,而请求失败又容易和布局问题混淆。Base URL 用https://taotoken.net/api,Key 用你控制台生成的,Model ID 按文档填。
排查时建议开两个窗口,一个看小程序真机日志,一个看后端请求日志。这样能快速区分是前端布局问题还是后端接口问题。大部分遮挡问题都是前端配置问题,把cursor-spacing和adjust-position调对,再配合键盘高度监听,基本都能解决。
6. 继续联调与模型验证的入口
输入框遮挡解决之后,下一步通常是把用户输入接到后端做处理。如果你需要验证模型对用户输入的理解能力,可以用模型对话页面快速测试,入口在https://taotoken.net/model-chat?utm_source=taotoken_aicg_blog_end&utm_content=model_chat&utm_campaign=rewrite。在里面输入几段典型用户消息,看模型返回是否符合预期,确认后再接到小程序后端。
如果你在做的是长期编码项目,需要反复调用模型做代码生成或自动化任务,可以看下 Coding Plan,入口在https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite。它适合那种调用量大、需要稳定配额的场景。
API Key 的管理在控制台,入口是https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite。建议给不同项目建不同的 Key,方便排查和限额。接入文档在https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite,里面有完整的参数说明和示例代码,遇到请求格式问题先翻文档。
最后提醒一句,小程序前端千万不要硬编码 API Key。正确链路是小程序调你的后端,后端持有 Key 并转发到 TaoToken。这样既安全,也方便你在后端做日志和限流。输入框遮挡是前端布局问题,接口联调是后端链路问题,两者分开排查,效率会高很多。