微信小程序解决ios页面上推问题
原创相信大家写原生小程序都遇到过一个问题,当输入框聚焦键盘弹起时,页面会自动上推,使得输入框刚好位于键盘之上,在安卓中推动的只是内容,但在ios中,推动的是整个页面,导致导航栏被推出屏幕外,如下:
针对这个问题,目前的解决方案是将自动上推改成手动上推,让我们自己来控制页面内容的滚动。
一、方案一
1.取消自动上推
微信小程序中的input和textarea都有一个属性 adjust-position ,将其改为 false
2.添加类名或者id
我们给每个输入框或者需要定位到键盘之上的元素添加唯一类名或者id,另外,我们还要给input或textarea添加自定义属性,值也为同一个类名或者id。
如上图,我期望键盘弹起能刚好将整个输入栏顶在键盘之上,所以我选择给这一栏加上唯一类名,里面的input自定义属性值为该输入栏的唯一类名,这样做事为了当我触发键盘事件时,能拿到当前输入栏的类名,获取该元素的坐标信息。
3.绑定键盘事件
input和textarea,微信小程序官方提供了键盘弹起的事件
这个方法里面的逻辑是本次的重点,主要是计算手动推动距离,先看代码:
// 监听页面软键盘弹起手动推动页面
bindkeyboardheightchange(e) {
// 键盘高度
const height = e.detail.height;
const className = e.target.dataset.class;
if (height === 0) {
this.scrollToInput(0);
return;
try {
this.createSelectorQuery()
.select(`.${className}`)
.boundingClientRect((res) => {
const windowHeight = this.data.windowHeight;
// 除去键盘的剩余高度
let restHeight = windowHeight - height;
// 元素左下角坐标
let bottom = res.bottom;
// 只有当元素被软键盘覆盖的时候才上推页面
if (bottom <= restHeight) return;
// 现阶段需要滚动的大小
let scrollTop = bottom - restHeight;
this.scrollToInput(height, scrollTop);
.exec();
} catch (error) {}
// 获取页面滚动条位置
getScrollOffset() {
return new Promise((resolve) => {
try {
wx.createSelectorQuery()
.selectViewport()
.scrollOffset((res) => {
resolve(res.scrollTop);
.exec();
} catch (error) {
resolve(0);
// 监听页面软键盘弹起手动推动页面
scrollToInput(keyboardHeight, scrollTop) {
this.setData({
keyboardHeight,
if (scrollTop) {
try {
this.getScrollOffset().then((lastScrollTop) => {
wx.pageScrollTo({
// 如果已经存在滚动,在此基础上继续滚
scrollTop: lastScrollTop ? lastScrollTop + scrollTop : scrollTop,
duration: 300,
} catch (error) {}
}
这里涉及到几个值,参见下图:
注意:这里的页面使用的是原生导航栏,若使用的是自定义导航栏,那么B/D/E/H都会再加上G区域,E/H在官方文档有说到,是元素基于显示区域的坐标位置。
-
键盘弹起后,获取到键盘的高度
C
,用显示区域B
减去键盘区域C
就是我们可使用的区域D
-
获取输入栏底部距离显示区域的坐标,如
E/H
-
若输入栏底部坐标小于可使用区域
D
,如H
,则说明当键盘弹起时,该输入栏不会被键盘遮挡,不需要推动 -
反之,若大于
D
,如E
,则说明键盘弹起时,输入栏会被键盘遮挡,这个时候就需要页面上推至输入栏完全展示出来 -
针对4,将
E
减去D
,得到一个差值F
,这就是当前元素距离完全展示还需要滚动的距离 -
页面实际滚动距离应该为
F
加上页面之前已经有的滚动距离,所以在滚动之前,需要再获取一次当前页面的滚动距离 - 这里可能会存在一个问题,页面的高度不够,无法滚动这么长的距离,因此,当键盘弹起时,这里需要给页面增加高度,这里直接是增加的键盘高度
<view style="height:{{keyboardHeight}}px"></view>
到这里,我们就已经实现了页面自动上推的功能了。另外,这里可以根据实际情况来做个判断,一般情况下,安卓我们可以直接使用原生的推动,即 adjust-position 为true,ios使用手动上推。
最终实现效果如下:
二、方案二
有些手机或者版本过低,监听不到键盘事件,可以使用聚焦事件和失焦事件代替,事件对象中也返回了键盘的高度。
步骤和逻辑处理与方案一相同的,如下:
// 监听页面软键盘弹起手动推动页面
bindfocus(e) {
const height = e.detail.height;
const className = e.target.dataset.class;
if (height === 0) {
this.scrollToInput(0);
return;
try {
this.createSelectorQuery()
.select(`.${className}`)
.boundingClientRect((res) => {
......
this.scrollToInput(height, scrollTop);
.exec();
} catch (error) {}
bindblur(e) {
this.scrollToInput(0);
}
对比两种方案 1. 肉眼观察,方案一的推动是及时的,方案二有一点点延迟,如下:
方案一 调试发现,他们的触发时机和滚动时机都差不多,但是键盘事件触发多次,而聚焦和失焦只会触发一次,大胆猜测,这可能就是上述问题的原因 2. 方案一键盘事件触发多次,可能每次获取到的高度和元素bottom不同,从而导致多次滚动,这里可以使用节流获取到第一次的数据即可 大家根据自己的需求选择使用哪一种方案方案二
三、疑难杂症
在一些特殊的场景下,还会有各种奇奇怪怪的问题
1、
问题
:在方案一中,如果
textarea
展示了原生完成,在点击完成时,或者失焦键盘落下事件未监听到
解决
:配合
bindblur
或者
bindconfirm
,将
keyboardHeight
设为0
// 监听页面软键盘弹起手动推动页面
scrollToInput(keyboardHeight, scrollTop) {
this.setData({
keyboardHeight,
if (scrollTop) {
......
bindblur(e) {
this.scrollToInput(0);
bindconfirm() {