uni-app 微信小程序通过Vue3 Hooks 实现动态填充页面剩余高度

2,504 阅读3分钟

应用场景

在uni-app开发微信小程序等项目时,经常会遇到这样的页面布局需求:上半部分高度固定,下半部分自动占满剩余高度,如下图所示应用场景:
上半部分为固定高度或内容填充高度的内容区域
下半部分为scroll-view滑动区域,可以无限下拉滚动进行数据加载

Nov-24-2022 16-18-59.gif

由于scroll-view实现纵向滚动需要设置高度height,如果设置一个固定的高度值将会使页面在不同屏幕尺寸下出现内容溢出屏幕或无法占满整个屏幕的情况,使页面看起来很不美观😅

因此为了提升用户体验,应该始终让整个页面保持在屏幕区域内固定,通过计算出页面的剩余高度,动态获取scroll-view高度,从而实现滚动组件的高度自适应。

实现思路🤔?

页面剩余高度 = 屏幕高度 - 上方区域高度

由于微信小程序并非运行在浏览器环境下,因此如果进行直接进行DOM操作在H5端运行正常,小程序端会报错❌

还好uni-app官方提供了uni.createSelectorQuery()nodesRef.boundingClientRect(callback)可以获取到小程序的节点和对应节点信息🎉

好的👌,那么大致的实现思路就已经确定了✅

  • 通过uni.getSystemInfoSync().windowHeight获取到屏幕高度ScreenHeight🖥
  • 给上方区域元素设置id="top"
  • 通过uni.createSelectorQuery().in().select('#top')获取上方区域节点
  • nodesRef.boundingClientRect(callback)获取上方节点的高度信息TopHeight
  • ScrollHeight = ScreenHeight - TopHeight 计算出剩余高度,即滑动组件高度

封装Hooks📦

Vue3提供了Hooks代替Vue2的mixins进行复用逻辑抽离,考虑到大部分的滚动列表使用场景可能都需要动态计算高度,因此通过Hooks进行代码抽离是很有必要的。

完整代码

// useScrollH.ts
/** Hooks 动态计算scrollList滑动区域高度
 * @param {Number} offset 可选 -offset偏移量 手动微调scroll高度
 * */ 
import { ref, getCurrentInstance } from 'vue'

export const useScrollHeight = (offset?: number):any => {
    const scrollHeight = ref<number>(0) // scroll组件高度
    const topHeight = ref<number>(0)    // 组件上方占用高度
    const currentInstance = getCurrentInstance();   // vue3绑定this
    const height = uni.getSystemInfoSync().windowHeight // 获取页面高度
    const topEl = uni.createSelectorQuery().in(currentInstance).select('#top')  // 获取#top元素
    topEl.boundingClientRect((data) => {    // 获取顶部高度
        topHeight.value = (data as any).height
        scrollHeight.value = height - topHeight.value - (offset || 0)   // 计算剩余高度 offset 偏移量
    }).exec()
    return scrollHeight
}

使用

<template>
    <view>
        <Tag id="top" />
        <view>
            <scroll-view :style="{'height': scrollH + 'px'}" scroll-y>
            </scroll-view>
        </view>
    </view>
</template>
import { ref, watchEffect } from 'vue';
import type { Ref } from 'vue';
import { useScrollHeight } from '@/config/utils/useScrollH';
import { onReady } from '@dcloudio/uni-app';
const scrollH = ref<number>(0) // scroll组件高度
...
// onReady中调用
onReady(() => {
    let scrollHeight:Ref<number>;
    scrollHeight = useScrollHeight()
    watchEffect(() => {
        scrollH.value = scrollHeight.value
    })
})

CSS实现

感谢评论区大佬提醒🙏,增加一种纯CSS实现scroll-view动态高度思路👇

  • 最外层page指定高度height:100%
  • 父元素设置display:flex,纵向布局flex-direction:columnheight:100vh,溢出隐藏overflow:hidden
  • scorll-view设置flex:1 overflow:scroll,随便给个初始高度,1px即可,不影响布局效果
<template>
    <view class="container">
        <view style="height: 100rpx; background-color: aquamarine;">我是标题</view>
        <scroll-view scroll-y class="scroll-container">
            <view v-for="item in 100" :key="item">{{item}}</view>
        </scroll-view>
        <view style="height: 100rpx; background-color: aquamarine;">我是结尾</view>
    </view>
</template>
page{
    height:100%;  // 重要
}
.container {
    display: flex;
    flex-direction:column;
    height:100vh;  // 重要 设成100%小程序端会失效
    overflow:hidden;
}
.scroll-container {
    flex: 1;
    overflow: scroll;  // 重要
    height: 0;
}

微信小程序端效果展示: Nov-28-2022 17-48-24.gif

更多使用场景及完整源码

小程序端获取动态高度可能存在延迟,本处使用watchEffect监听到变化后立即执行,也可以考虑使用setTimeout进行延迟获取。

注意⚠️

1.获取节点高度首先要等节点渲染完成,因此要在onReady中调用
2.注意小程序端可能存在延迟,考虑使用watchEffectsetTimeout延迟获取