活动报名、留资表单在 iPhone 的 Safari 或微信里很常见:键盘顶起视口,点空白收起键盘后,整页却还吊在上面,底色或背景图对不齐。本例在输入控件失焦后,用一次很短的 scrollTo 把滚动值往回拨 1 像素,强迫浏览器重算视口。
活动页里哪些输入框需要监听失焦
键盘是跟着「当前焦点」走的。用户点完成、点空白,焦点离开控件,才表示键盘要收。需要覆盖活动页里会弹出键盘的三类:input、select、textarea。只绑某一个 id,动态插进去的框、下面另外一个手机号框都照顾不到。
事件挂在 body 上委托,后插入的表单项也能用:

jQuery 必须写在 iosKeyboard.js 前面。样式只为演示输入框,回位不依赖某一套 UI。上方 .page-fill 把输入框顶到屏幕下面,方便在真机上滚过去再点,更容易看出顶起和回位。
键盘收起后页面为何还停在上方
iOS 弹出键盘时,会缩小可视区域,并把焦点所在的输入框顶进键盘上方。键盘下去以后,部分机型、微信内置浏览器不会把 scrollTop 完全还原,视觉上就像页面被顶住、底部空一截。
所以不能只靠 CSS。要在失焦这一刻,读当前滚动位置,再主动写回一个非常接近的值,让页面重新贴回键盘出现前的位置。Android 多数没有这个问题,这段逻辑放在活动页里一般也无副作用:多滚 1 像素用户看不出来。
用滚动位置把页面拉回来怎么写
先取 document.documentElement.scrollTop,没有再取 document.body.scrollTop,兼容不同内核。然后 window.scrollTo(0, Math.max(scrollHeight - 1, 0)):横坐标 0,纵坐标减 1,小于 0 时按 0,避免滚到负值。

减 1 是故意的:和当前值完全相同,有的 WebView 会忽略这次滚动,页面仍卡住。我第一次看也卡了一下,说白了就是骗浏览器再排一次版。
延时 100 毫秒起什么作用
失焦和键盘动画不是同一帧结束的。立刻 scrollTo,键盘可能还在往下收,随后系统又改一次滚动,刚写回去的值被盖掉。setTimeout(..., 100) 等键盘收束后再拨滚动。
100 是经验值,本例沿用。个别机型仍偶发错位,可试 150、200,不必一上来加很长的延时,否则用户会觉得收键盘后页面顿一下。这坑我见过不止一次:延时写成 0,真机上时好时坏。
报名表单里如何套用这段逻辑
把 js/iosKeyboard.js 引进活动页,保证 jQuery 在前。表单里的手机号、姓名、下拉、多行备注都会走到失焦回位,不用给每个框单独绑。固定底栏提交按钮若被顶起,回位后一般会跟着回来;若底栏是 position: fixed,另按设计看是否要在聚焦时加底垫,和这一段不冲突。

延时和减 1 像素一般不用改。固定底栏若另有贴底逻辑,与失焦回位分开写,不要揉进这一次 scrollTo。