处理UniApp微信小程序页面输入框被弹出的键盘遮挡住

3,157 阅读2分钟

描述问题

正常情形是,执行某项动作弹出popup,点击textarea组件输入框弹出软体键盘。实际情况执行上述动作弹出的软体键盘挡住了输入框,无法正常在输入文字内容。展示如下图:

Snipaste_2023-03-03_16-37-29.png

解决问题

解决思路

按不同的情况去分析:页面有滚动条、页面无滚动条

  1. 页面无滚动条
    moveTop等于 100vh - (kbdHeight + inputBottomY ),三个值的运算结果就是popup需要向上移动的值(负值)。
    基本流程:监听输入框focus事件;计算节点inputBottomY的值;edit-template-item组件触发moveTop事件this.$emit('moveTop', { top: moveTop });页面指定方法接收moveTop事件的传值,并使popup向上移动 moveTop (px)。

  2. 页面有滚动条
    基于上面的无滚动条版本,发现在一屏内就很容易处理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: falseoldScrollTop: 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无明显卡顿情况)