1. 安卓输入法遮挡 input 的真实场景与排查思路
小程序里 input 被输入法挡住,是个看起来很小、真机上却能把人折腾半天的坑。我最近在做一个带备注输入的表单页,iOS 上一切正常,到了几台安卓机上就出问题:点进输入框,键盘弹起来,输入框要么被压在键盘下面,要么页面往上顶了一大截,顶部标题被顶出屏幕,用户根本看不到自己在打什么。更离谱的是,同一份代码在不同安卓机型上表现还不一样,有的正常上推,有的完全不推,有的推了但推错位置。
这个问题的核心检索词就是「小程序 input 被输入法遮挡」,它属于典型的跨端兼容问题。小程序官方给 input 提供了adjust-position属性,默认值是true,意思是键盘弹起时自动把页面往上推,让输入框露出来。理论上够用,但安卓阵营的输入法实现五花八门,系统 WebView 版本、厂商定制 ROM、输入法 App 自身的高度上报策略都会影响最终效果,所以才会出现「部分安卓机不兼容」。
适合谁看:正在做小程序表单、聊天输入、验证码输入、备注弹窗的开发者,尤其是遇到「iOS 正常、安卓翻车」这种情况的同学。下面我会从三条线索切入——adjust-position配置、页面滚动位置、键盘高度监听,给出可直接复制的代码和真机验证步骤。
先说排查思路,别一上来就改代码。第一步,确认问题机型:是全部安卓还是个别机型?是系统输入法还是第三方输入法(搜狗、讯飞等)?第二步,确认页面结构:input 是普通文档流,还是放在position: fixed的弹层里?这两种情况处理方式完全不同。第三步,打开真机调试,看bindfocus回调里event.detail.height到底有没有值、值是多少。很多遮挡问题的根因,就是键盘高度没拿到或者拿到了 0。
我踩过的坑是:一开始只加了cursor-spacing,以为能解决,结果在部分机型上输入框确实浮起来了,但浮的位置不对,和键盘之间留了一条缝或者直接重叠。后来才明白,cursor-spacing控制的是光标与键盘的距离,它依赖系统正确上报键盘高度,一旦上报不准,这个属性就形同虚设。所以真正稳的方案,是关掉自动上推,自己监听键盘高度,手动控制输入框位置。这也是下面要展开的重点。
2. TaoToken 前置准备:用 API 调试输入法兼容问题
在动手改代码之前,先解决一个现实问题:真机调试时,我们经常需要对比不同模型对同一段兼容代码的分析,或者让模型帮忙读一段报错日志。这时候一个稳定的 API 入口就很有用。TaoToken 提供统一的模型调用接口,官网地址是 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,API 入口是 https://taotoken.net/api (这个不加 UTM)。
它的定位是给开发者提供一个聚合式的模型调用层,你不需要在多个平台之间来回切换 Key,用一套 Base URL 和 Key 就能调用不同模型。对于排查输入法遮挡这种需要反复试错、对比方案的场景,能省不少事。比如你可以把bindfocus回调里打印出来的event.detail贴给模型,让它帮你判断键盘高度是否异常。
接入方式很简单,三步:拿 Key、配 Base URL、选 Model ID。这三件套在任何支持自定义 API 的工具里都是通用的。如果你用的是 Claude Code 这类编码工具,可以在它的配置里填入:
{ "base_url": "https://taotoken.net/api", "api_key": "你的_API_KEY", "model": "claude-sonnet-4-20250514" }如果你用的是 Cline 或者带 MCP 的编辑器插件,配置项名字可能不同,但核心还是这三样:Base URL 填https://taotoken.net/api,Key 填你在控制台生成的,Model ID 按你需要的模型填。注意 Base URL 不要带 UTM 参数,那是给网页访问用的,API 调用只认纯域名路径。
拿 Key 的入口在控制台的 API Keys 页面,模型对话可以在对话页直接试,长期做编码和 Agent 任务的话可以看 Coding Plan。这几个入口分别是:
- 模型对话:https://taotoken.net/models?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
- 控制台:https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_content=console&utm_campaign=rewrite
- API Keys:https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite
- 接入文档:https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite
为什么要在这里提这个?因为输入法遮挡的排查过程,本质是「改代码 → 真机验证 → 看日志 → 再改」的循环。有一个顺手的模型调用入口,能帮你快速理解event.detail.height的异常值、生成对比测试用例、解释不同安卓 WebView 的行为差异。它不是解决遮挡问题的直接工具,但能明显加快你的排查节奏。
需要说明的是,TaoToken 在这里的角色是开发辅助,不是让你把生产环境的用户输入直接打到模型上。排查阶段用它来分析日志、生成测试代码是合适的,涉及用户隐私的数据不要往外传。
3. 可复制配置:adjust-position 关闭 + 键盘高度监听 + 滚动补偿
这一节是核心,直接给能跑的代码。整体思路分三步:第一,把 input 的adjust-position设为false,关掉系统自动上推,避免它和我们的手动控制打架;第二,在bindfocus里拿键盘高度,赋给外层容器的bottom;第三,在bindblur里把高度归零。如果输入框在弹层里,还要配合滚动补偿。
先看 WXML 结构。假设输入框放在一个固定在底部的弹层里:
<view class="wx-verify-main" style="bottom:{{reasonHeight}}px;"> <input class="key-input" value="{{inputValue}}" type="number" focus="{{isFocus}}" bindinput="listenKeyInput" adjust-position="{{false}}" show-confirm-bar="{{false}}" cursor-spacing="0" bindfocus="bindfocusDialog" bindblur="bindblurDialog" /> </view>这里几个属性逐个说明。adjust-position="{{false}}"是关键,关掉自动上推。cursor-spacing="0"让光标紧贴键盘上沿,配合手动定位使用。show-confirm-bar="{{false}}"在数字键盘场景下隐藏确认栏,减少高度计算误差。bindfocus和bindblur分别处理聚焦和失焦。
对应的 JS:
Page({ data: { inputValue: '', isFocus: false, reasonHeight: 0 }, bindfocusDialog(event) { const vm = this; // event.detail.height 是键盘高度,部分机型可能为 0 const keyboardHeight = event.detail.height || 0; vm.setData({ reasonHeight: keyboardHeight }); }, bindblurDialog(event) { this.setData({ reasonHeight: 0 }); }, listenKeyInput(event) { this.setData({ inputValue: event.detail.value }); } });外层容器用position: fixed; bottom: {{reasonHeight}}px;定位,键盘弹起时bottom等于键盘高度,输入框就正好贴在键盘上方。键盘收起时bottom归零,回到页面底部。
但这里有个坑:部分安卓机event.detail.height返回 0 或者明显偏小。这时候需要加一层兜底。可以在bindfocus里判断,如果高度为 0,用一个经验值或者通过wx.getSystemInfoSync()的screenHeight和windowHeight差值来估算:
bindfocusDialog(event) { let keyboardHeight = event.detail.height || 0; if (keyboardHeight === 0) { const sysInfo = wx.getSystemInfoSync(); // 用屏幕高度减去可视窗口高度,粗略估算键盘高度 keyboardHeight = sysInfo.screenHeight - sysInfo.windowHeight; } this.setData({ reasonHeight: keyboardHeight }); }如果输入框不在 fixed 弹层里,而是在普通文档流中,那bottom定位就不适用了。这时候改用滚动补偿:在bindfocus里拿到键盘高度后,用wx.pageScrollTo把输入框滚到可视区域。可以先通过createSelectorQuery拿到输入框的位置:
bindfocusDialog(event) { const keyboardHeight = event.detail.height || 0; const query = wx.createSelectorQuery(); query.select('.key-input').boundingClientRect(); query.selectViewport().scrollOffset(); query.exec((res) => { if (!res[0]) return; const inputBottom = res[0].bottom; const scrollTop = res[1].scrollTop; const windowHeight = wx.getSystemInfoSync().windowHeight; const visibleBottom = windowHeight - keyboardHeight; if (inputBottom > visibleBottom) { const offset = inputBottom - visibleBottom + 20; wx.pageScrollTo({ scrollTop: scrollTop + offset, duration: 200 }); } }); }这段逻辑是:算出输入框底部和「可视区域底部(窗口高度减键盘高度)」的差值,如果输入框被挡住了,就往下滚相应的距离,多留 20px 缓冲。这样即使键盘高度上报有偏差,也能保证输入框露出来。
配置层面还有一个容易忽略的点:页面本身的page.json里如果开了"disableScroll": true,wx.pageScrollTo会失效,滚动补偿就用不了。这种页面只能走 fixed 弹层方案。所以先确认你的页面配置。
4. 真机验证:从 bindfocus 日志到成功结果
代码写完不算完,必须真机验证。模拟器的键盘行为和真机差别很大,尤其是安卓,模拟器基本测不出遮挡问题。验证步骤如下。
第一步,打开真机调试。在微信开发者工具里点「真机调试」,用手机扫码。然后在手机上手点输入框,观察开发者工具的 Console 面板,看bindfocus有没有触发、event.detail.height打印出来是多少。建议在bindfocusDialog里加一行console.log('keyboard height:', event.detail.height),方便对照。
第二步,记录三组数据:iOS 上的键盘高度、正常安卓机的高度、出问题机型的高度。正常情况下,数字键盘高度在 250 到 350 之间(单位 px,不同分辨率有差异)。如果某台机器返回 0 或者 100 以下,基本可以确定是键盘高度上报异常,需要走兜底逻辑。
第三步,验证输入框位置。键盘弹起后,输入框应该紧贴键盘上沿,中间没有明显缝隙,也没有重叠。如果输入框浮得太高,说明reasonHeight偏大;如果还被挡住,说明偏小或者滚动补偿没生效。可以临时把reasonHeight显示在页面上,方便肉眼观察:
<view style="position: fixed; top: 10px; left: 10px; z-index: 999;"> height: {{reasonHeight}} </view>第四步,测试边界情况。连续快速点击不同输入框,看bindblur和bindfocus是否成对触发,reasonHeight有没有残留。切换输入法(系统输入法 ↔ 第三方输入法),看高度是否变化。旋转屏幕(如果支持横屏),看定位是否错乱。这些边界情况往往是线上问题的来源。
第五步,验证失焦后的恢复。键盘收起后,reasonHeight应该归零,输入框回到原位,页面滚动位置也应该恢复正常。如果页面停在滚动后的位置回不去,需要在bindblur里补一个wx.pageScrollTo回到原位置,或者记录聚焦前的scrollTop,失焦时还原。
成功的结果是:在之前出问题的安卓机型上,点输入框,键盘弹起,输入框稳稳贴在键盘上方,页面没有异常跳动,输入内容可见,收起键盘后一切复原。如果达到了这个效果,说明方案生效。
这里补充一个验证技巧:用wx.onKeyboardHeightChange监听键盘高度变化。这个 API 在部分基础库版本上可用,能实时拿到键盘高度,比bindfocus的一次性上报更可靠。用法:
wx.onKeyboardHeightChange((res) => { this.setData({ reasonHeight: res.height }); });注意这个监听是全局的,要在onLoad里注册,onUnload里用wx.offKeyboardHeightChange取消,避免内存泄漏。它和bindfocus方案可以二选一,也可以结合使用,以onKeyboardHeightChange为准,bindfocus作为兜底。
5. 常见报错排查:401、local proxy failed、reading choices、OAuth
排查过程中,除了遮挡本身,还会遇到一些和 API 调用、工具配置相关的报错。这些报错容易让人误以为是代码问题,其实是配置没对上。逐个说。
401 Unauthorized:这个最常见,出现在你调用模型 API 时。原因通常是 API Key 填错、Key 过期、或者 Base URL 和 Key 不匹配。检查三件套:Base URL 是不是https://taotoken.net/api,Key 是不是从控制台 API Keys 页面复制的完整字符串,Model ID 是不是当前账号有权限的模型。注意 Base URL 末尾不要多加/v1之类的路径,除非文档明确要求。如果用的是 Claude Code,检查它的配置文件里base_url和api_key字段名是否正确。
local proxy failed:这个报错通常出现在本地工具尝试走代理连接时。如果你在工具里配置了本地代理端口,但代理服务没启动,就会报这个。解决方法是检查工具的代理设置,把不需要的代理关掉,直连https://taotoken.net/api。注意这里说的是工具自身的网络配置,不是让你去搞什么网络加速,就是普通的本地端口检查。
reading choices 相关报错:这类报错一般出现在解析模型返回结构时,比如Cannot read property 'choices' of undefined。原因是返回体不是预期的 OpenAI 兼容格式,可能是请求路径不对、或者模型返回了错误信息。先打印完整返回体看error字段,再对照接入文档确认请求格式。常见错误是请求体里model字段拼错,或者messages数组格式不对。
OAuth 相关报错:如果你用的是需要 OAuth 授权的工具(比如某些 CLI),报 OAuth 失败通常是回调地址不对或者 token 过期。检查工具的 OAuth 配置,重新走一遍授权流程。如果工具支持 API Key 模式,优先用 API Key,比 OAuth 少一层坑。
把这几类报错和输入法遮挡问题分开看:遮挡是前端渲染问题,报错是 API 调用问题,两者不要混在一起排查。建议先确保 API 调用通了(能正常拿到模型返回),再专心调前端定位。否则你会分不清是键盘高度没拿到,还是请求根本没发出去。
排查时养成看日志的习惯。bindfocus里打印event.detail,API 调用处打印完整请求和响应,工具配置处确认三件套。日志清晰了,问题定位就快。
6. 语义一致收尾:把方案落到你的项目里
回到最开始的问题:小程序 input 被输入法遮挡,本质是系统自动上推在部分安卓机上不可靠。解决方案不是找一个万能属性,而是关掉自动上推,自己接管键盘高度和输入框定位。核心三件套是adjust-position="{{false}}"、bindfocus拿高度、bindblur归零,配合滚动补偿处理文档流场景。
落地时按这个顺序来:先确认页面结构(fixed 弹层还是文档流),选对应方案;再确认基础库版本,决定用bindfocus还是onKeyboardHeightChange;然后加兜底逻辑处理高度为 0 的机型;最后真机验证,覆盖系统输入法和第三方输入法。
如果你在排查过程中需要快速对比不同模型对兼容代码的分析,或者让模型帮你读日志,可以用 TaoToken 的 API 入口 https://taotoken.net/api ,Key 在 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=api_keys&utm_campaign=rewrite 拿,接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_content=doc&utm_campaign=rewrite 。长期做编码和 Agent 任务的话,Coding Plan 入口是 https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=coding_plan&utm_campaign=rewrite 。
最后留一个实用技巧:把reasonHeight的兜底逻辑封装成一个函数,不同页面复用。输入框多的时候,给每个 input 绑定同一套bindfocus/bindblur,用data-属性区分是哪个输入框,避免重复写逻辑。这样下次再遇到新的安卓机型,改一处就够了。