描述问题
正常情形是,执行某项动作弹出popup
,点击textarea
组件输入框弹出软体键盘。实际情况执行上述动作弹出的软体键盘挡住了输入框,无法正常在输入文字内容。展示如下图:
解决问题
解决思路
按不同的情况去分析:页面有滚动条、页面无滚动条
-
页面无滚动条
moveTop等于100vh
- (kbdHeight
+inputBottomY
),三个值的运算结果就是popup需要向上移动的值(负值)。
基本流程:监听输入框focus
事件;计算节点inputBottomY的值;edit-template-item
组件触发moveTop
事件this.$emit('moveTop', { top: moveTop })
;页面指定方法接收moveTop
事件的传值,并使popup向上移动moveTop
(px)。 -
页面有滚动条
基于上面的无滚动条版本,发现在一屏内就很容易处理popup需要向上移动的量。那么就“变着法子”继续沿用上面的策略。
如何变法子呢?
第一步:监听页面的滚动(被id="demo"包裹的部分),并把scrollTop
值传给子组件edit-template-item
;
第二步:edit-template-item
触发一个scroll
事件,让页面滚动到顶部(处于没有发生滚动的状态);
第三步:再次计算输入框节点inputBottomY的值(mounted
的时候计算过一次);
第四步:并使popup向上移动moveTop
(px)(moveTop的值计算请参考下面edit-template-item组件代码);
第五步:输入框失去焦点时,使页面的滚动条从顶部滚动到之前的位置同时使popup恢复到之前的位置;
实际代码
当前页面
<template>
<view id="demo">
<view class="mainContent">
<!-- 省略中间的内容 -->
</view>
<u-popup mode="bottom" :custom-style="popupStyle" height="90%">
<!-- 根据需求弹出框只可以在两个组件直接切换 -->
<template-list></template-list>
<edit-template-item
:scrollTop="scrollTop"
@moveTop="movePopFixedTop"
@scroll="makePageScroll"
>
</edit-template-item>
<u-popup>
</view>
</template>
<script>
export default {
data() {
return {
popupStyle: { top: 0 },
scrollTop: 0
}
},
methods: {
// 监听页面滚动,并把值传递给edit-template-item组件
onPageScroll(e) {
this.scrollTop = e.scrollTop;
},
// 接收edit-template-item触发的scroll事件,使页面滚到顶部或是复原到上一次滚动的位置
makePageScroll(data) {
uni.pageScrollTo({
selector: '#demo',
scrollTop: data.scrollTop,
duration: 15
});
},
movePopFixedTop(data) {
this.makePageScroll();
this.$nextTick(() => {
// 使popup向上移动edit-template-item触发的moveTop事件传递的值moveTop
this.$set(this.popupStyle, 'top', data.top);
})
}
}
</script>
edit-template-item
组件
<script>
export default {
props: {
scrollTop: {
type: Number,
default: 0
}
},
mounted() {
this.getPointLoaction();
},
data() {
return {
inputBottomY: 0,
hasRecord: false,
oldScrollTop: 0
}
},
methods: {
// 获取输入框的节点位置信息
getPointLoaction() {
this.$nextTick(() => {
const query = uni.createSelectorQuery().in(this);
query.select('#iputTextarea').boundingClientRect(data => {
this.inputBottomY = data.bottom;
}).exec();
})
},
// 输入框聚失去焦点时,滚动条滚动陈到上次的位置,popup移动到上一位置
onBlurInput() {
this.hasRecord = false;
// 页面滚动到原来的位置
this.$emit('scroll', { scrollTop: this.oldScrollTop, });
this.$nextTick(() => {
// popup恢复到原来的位置
this.$emit('moveTop', { top: 0 });
})
},
// 输入框聚焦时执行的操作
onFocusInput(event) {
if (!this.hasRecord) {
this.hasRecord = true;
// 记录上一次的滚动scrollTop值,用于输入框失去焦点时页面滚动到原来的位置
this.oldScrollTop = this.scrollTop;
setTimeout(() => {
// 因为节点位置可能已经改变,再次获取节点位置
this.getPointLoaction();
const kbdHeight = event.detail.height;
// 页面发生过滚动 且滚动位置没被还原
if (this.oldScrollTop > 0) {
// 让显示弹窗的当前页面,处于无滚动状态的一屏幕内
this.$emit('scroll', { scrollTop: 0 });
this.$nextTick(() => {
const query = uni.createSelectorQuery().in(this);
query.select('#iputTextarea')
.boundingClientRect(data => {
this.inputBottomY = data.bottom;}).exec();
})
}
let moveTop = 'calc(100vh - ' + Number(kbdHeight + this.inputBottomY) + 'rpx' + ')';
this.$emit('moveTop', { top: moveTop });
}, 25)
}
},
}
}
</script>
上面大概就是解决的方法的具体代码细节。注意上面代码添加一个hasRecord
的值,用于记录是否点触发过focus
事件。已经触发了该事件在软体键盘没有收起且没有监听到blur
事件时,再次触发focus
事件,不需要执行任何操作。假设没有一个记录是否点触发过focus
事件的值,则就是每次点击输入框(未失去焦点之前
),都会让popup上移一个值(每次移动的值不相同,因为inputBottomY都不相同
)。
注意事项
- 使用 uni.createSelectorQuery() API获取节点位置相关信息,需要在生命周期 mounted 后进行调用。
- 建议使用uni-app的textarea组件,监听
focus
事件获取键盘高度。(uni.onKeyBoardHeightChange
针对本人我使用 Android版本 13,经常在键盘已经弹出的情况下,无法监听成功软体键盘高度的变化) - textarea组件或是uviewui的input组件不要置于uni-App的scroll-view组件内部 ,会导致整个小程序都极其卡顿,必须重新进入才能解决卡顿问题。(针对本人我使用 Android版本 13 就会很卡顿,测试同事使用的iphone无明显卡顿情况)