概述
用户点击输入框时键盘会弹起,若处理不当容易出现页面被压缩、底部按钮上浮、无法滚动等问题。本文按常见业务场景(如全屏表单、弹窗内输入、多输入框等),依照涂鸦 UED 规范整理了一套做法和可运行 Demo,方便直接复用并避开常见坑。Github demo 源码
环境要求
说明: 文中涉及的
ty.enablePageScroll等 API 仅在 APP 7.3.0 及以上版本支持,低版本请做好兼容处理。
一、业务场景与要点速览
场景选择指引
flowchart TD
A["是否在弹窗内?"] -->|否| B["输入框数量?"]
A -->|是| C["弹窗类型?"]
B -->|"单个/少量"| D["场景1: 浏览/轻量输入框 + 键盘自动收起"]
B -->|多个| E["场景2: 多输入框表单 + 键盘固定"]
B -->|"卡片布局"| I["是否超出一屏?"]
I -->|否| J["场景6: 卡片固定高度 + 一屏固定"]
I -->|是| K["场景7: 卡片自适应 + 超出一屏滚动"]
C -->|"系统弹框"| H["场景5: 弹窗聚焦"]
C -->|"自定义弹窗 + 少量输入框"| F["场景3: 弹框轻量输入框 + 键盘自动收起"]
C -->|"自定义弹窗 + 多输入框"| G["场景4: 弹窗多输入框 + 键盘固定"]二、通用最佳实践
以下通用知识会在各场景中反复使用,建议先行了解。
1. useWindowHeight — 获取安全的一屏高度
100vh 代表整个 WebView 容器高度,但在安卓设备上键盘弹出时 WebView 会被压缩,导致底部内容上浮。为了让页面在聚焦时仍保持整屏高度,需要获取一个固定的像素值。
注意事项:
- 通过
getSystemInfoSync().useableWindowHeight可获取页面容器可用高度 - 安卓设备在页面初始化时
useableWindowHeight值为 0(逻辑层异步获取容器高度),因此需要滞后获取 - 推荐在第一次聚焦事件时获取
建议封装为自定义 Hook:
后续各场景代码中将直接使用
useWindowHeight,不再重复获取逻辑。
2. 键盘自动收起
通过 Input 组件的受控 focus + 容器 onTouchMove 调用 setFocus(false) 即可。
注意: 如果内部有阻止冒泡的行为,
onTouchMove事件可能无法触发。
3. 聚焦单容器滚动 [iOS]
iOS 聚焦时会默认在 WebView 外层包裹一层滚动容器。如果页面内部也使用了 ScrollView,会出现双层滚动容器——滚动过快时页面可能卡住。
解决方案: 使用页面级滚动,在 index.config.ts 中设置:
此时顶部导航、底部按钮等需要用 fixed 定位,中间的内容部分上下预留 padding 滚动即可。
4. 弹窗相对外部页面位置固定 [Android]
安卓设备在弹窗场景聚焦时,可以通过让根节点使用 100vh 作为计算基准,让容器自适应压缩,保持弹窗上下位置不变。
iOS 的聚焦位置不可控,此方案仅对 Android 有效。
5. 聚焦禁止滚动 [iOS]
iOS 在聚焦时默认可滚动,且无法控制。在 APP 7.3.0 及以上版本中,可在 onFocus 回调内调用:
注意:一旦设置了不可滚动,非聚焦场景也会生效。建议在 onBlur 时恢复:
关于 @ts-ignore 的说明
文档中部分 API 调用可能出现 TypeScript 类型报错,原因如下:
推荐做法: 通过模块声明扩展类型,避免直接使用 @ts-ignore:
三、各场景要点详解
1. 浏览/轻量输入框表单型 + 键盘自动收起
键盘出现时,滑动页面自动收起键盘
利用 Input 组件的 focus 属性 + 容器 onTouchMove,滚动页面时输入框自动失焦收起键盘。(详见"通用最佳实践 — 键盘自动收起")
页面高度使用 useWindowHeight [Android]
使用 useWindowHeight Hook 获取准确的一屏高度,防止安卓设备键盘弹出后页面压缩。(详见"通用最佳实践 — useWindowHeight")
底部按钮位置固定 [Android]
禁止使用 position: fixed,否则安卓键盘弹出时按钮会上浮。
可以使用根节点固定高度 + position: relative,按钮区域使用 position: absolute;或者 flex 自适应布局充满全屏。
2. 多输入框表单型 + 键盘固定
底部按钮固定贴底,键盘出现时安卓自动上浮 [Android]
底部操作区使用 position: fixed 定位,不随内容滚动。记得给滚动容器预留底部 padding,防止底部按钮遮挡滚动内容。
避免双层滚动容器 [iOS]
iOS 默认聚焦时会给小程序容器外层包裹一层滚动容器,如果页面内部也有滚动容器,容易出现双层滚动——滚动过快时页面卡住。建议使用页面级滚动。(详见"通用最佳实践 — 聚焦单容器滚动")
在 index.config.ts 中设置:
如果采用自定义导航栏,则设置 position: fixed 吸顶,内容区通过 CSS 变量预留 statusBar + navbar 高度,避免被遮挡:
保持键盘常驻
同系统默认行为,无需额外处理。
3. 弹框轻量输入框表单型 + 键盘自动收起
弹窗内受控 focus + onTouchMove 失焦
与场景 1 的"键盘自动收起"一致。Popup 内使用 ScrollView + 受控 focus,在弹窗内容容器上监听 onTouchMove,聚焦时失焦收起键盘。
使用 useWindowHeight 计算底部按钮位置 [Android]
安卓机上使用 useWindowHeight 获取高度,并计算按钮 top,保证相对可视区域固定。
弹框相对外部容器位置固定 [Android]
不可以设置固定的 px 或 rpx 值,会导致安卓设备键盘出现时弹框被向上顶。设置 calc(100vh - 50px) 的高度可以让容器自适应压缩。(详见"通用最佳实践 — 弹窗相对外部页面位置固定")
4. 弹窗多输入框表单型 + 键盘固定
Popup 控制背景可滚动
使用 lockScroll={false} 允许背景滚动。在键盘聚焦场景,用户可以通过慢速拖动使最外层容器滚动。
弹窗内底部按钮在键盘弹起时自动上浮 [Android]
安卓设备上使用 position: fixed 布局,让键盘弹出时底部按钮刚好上浮到键盘上方。记得中间滚动容器内预留底部空间,防止按钮遮挡内容。
弹框位置相对外部页面固定 [Android]
不可以设置固定的 px 或 rpx 值,会导致安卓设备键盘弹出时整个弹框被向上顶。设置 calc(100vh - 50px) 的高度可以让容器自适应压缩。(详见"通用最佳实践 — 弹窗相对外部页面位置固定")
5. 弹窗聚焦
弹框输入框聚焦期间页面禁止滚动
方案 A:使用 SmartUI Dialog 组件(推荐)
调用 SmartUI 的 DialogInstance.input 系统级输入弹框,其内部已禁用页面滚动,无需额外处理。
常用参数示例:
方案 B:使用自定义弹框
如果使用自定义弹框,需要手动管理页面滚动状态:
注意: 弹框关闭后务必恢复滚动,否则页面将无法滚动。
6. 卡片高度固定 + 一屏固定 + 键盘固定
容器高度使用 useWindowHeight,严格一屏
通过 useWindowHeight 设置容器高度,避免使用 100vh 导致键盘出现后容器被压缩。
Textarea 聚焦时禁用页面滚动 [iOS]
在 Field 的 onFocus 中调用 ty.enablePageScroll({ scrollEnable: false }),阻止 iOS 系统在聚焦时的默认可滚动行为。
底部按钮相对一屏容器绝对定位
按钮使用 position: absolute + bottom,相对固定高度的根容器定位,防止键盘出现时上浮。
7. 卡片高度自适应 + 超出一屏滚动 + 键盘固定
容器高度 useWindowHeight,内部 ScrollView 占满剩余空间
外层容器用 useWindowHeight 定高,内层 ScrollView scrollY 使用 flex: 1 占满,实现一屏内滚动。
Field textarea 使用 autosize,卡片高度随内容变化
通过 autosize 的 maxHeight / minHeight 控制多行输入高度,避免固定高度导致内容被裁切或留白过多。
底部按钮在 ScrollView 外,保持贴底
按钮容器放在 ScrollView 外、根容器内,使用 flex 或固定高度,键盘弹起时仍贴底,不被顶出视野。
四、常见问题(FAQ)
Q1: 为什么安卓设备首次获取 useableWindowHeight 为 0?
安卓上逻辑层是异步获取容器高度的,页面初始化时尚未完成获取。建议在首次聚焦事件中获取,此时容器高度已就绪。
Q2: lockScroll={false} 会不会导致背景页面跟着滚动?
会。设置 lockScroll={false} 后,用户慢速拖动时可能触发外层页面滚动。如果不希望这种行为,请保持默认的 lockScroll={true},但需注意这可能影响弹窗内的键盘聚焦体验。
Q3: 为什么设置了 disableScroll: false 页面还是不能滚动?
检查以下几点:
- 页面内容高度是否确实超过了视口高度
- 是否在其他地方调用了
ty.enablePageScroll?.({ scrollEnable: false })但未恢复 - 是否有
overflow: hidden等 CSS 样式阻止了滚动
Q4: iOS 和 Android 在聚焦场景的主要差异是什么?
Q5: 什么时候用 position: fixed,什么时候用 position: absolute?
fixed:用于期望按钮随键盘上浮的场景(如场景 2、4),按钮相对于视口定位absolute:用于期望按钮位置不变的场景(如场景 1、6),按钮相对于固定高度的父容器定位