提醒
本文实例是使用uniapp进行开发演示的。

一、需求场景

在开发详情页面时,不同产品描述文案不同,有的文案比较长,需求上要求描述文案最多展示4行文案,少于4行文案,全部显示,此UI高度自动适配,例如,只有一行文案,那么UI高度也是展示一行的高度,超过的部分文案不显示同时右边显示查看详情、查看更多或者展开等按钮,让用户点击此按钮进行查看。

二、需求分析

  1. 详情页面是通用的,显示描述的UI的高度是可变化的,最多是4行文案的高度;
  2. 实时监听描述文案的属性变化;
  3. 实时更新UI高度以及对应的UI样式;
  4. 需要知道什么时候DOM更新完成的;

三、技术方案

  1. 设置css属性:max-height: xxx; overflow: hidden;控制描述文案UI的最大高度和超过的文案隐藏起来;
  2. 使用vue的watch函数监听描述文案的属性变化;
  3. 使用uni.createSelectorQuery()获取节点信息(描述文案UI的高度);
  4. 使用vue的nextTick()函数等待 DOM 更新完成。

四、技术知识点简介

4.1 vue的watch函数

用于声明在数据更改时调用的侦听回调。
watch 选项期望接受一个对象,其中键是需要侦听的响应式组件实例属性 (例如,通过 data 或 computed 声明的属性)——值是相应的回调函数。该回调函数接受被侦听源的新值和旧值。

除了一个根级属性,键名也可以是一个简单的由点分隔的路径,例如 a.b.c。注意,这种用法不支持复杂表达式——仅支持由点分隔的路径。如果你需要侦听复杂的数据源,可以使用命令式的 $watch() API。

值也可以是一个方法名称的字符串 (通过 methods 声明),或包含额外选项的对象。当使用对象语法时,回调函数应被声明在 handler 中。额外的选项包含:

  • immediate:在侦听器创建时立即触发回调。第一次调用时,旧值将为 undefined。
  • deep:如果源是对象或数组,则强制深度遍历源,以便在深度变更时触发回调。详见深层侦听器。
  • flush:调整回调的刷新时机。详见回调的触发时机及 watchEffect()。
  • onTrack / onTrigger:调试侦听器的依赖关系。详见侦听器调试。

注意:声明侦听器回调时避免使用箭头函数,因为它们将无法通过 this 访问组件实例。

点击查看侦听器详情

4.2 uni.createSelectorQuery()

uni.createSelectorQuery()主要是获取节点信息。
返回一个 SelectorQuery 对象实例。可以在这个实例上使用 select 等方法选择节点,并使用 boundingClientRect 等方法选择需要查询的信息。

注意:

  • 使用 uni.createSelectorQuery() 需要在生命周期 mounted 后进行调用。
  • 默认需要使用到 selectorQuery.in 方法。
  • 支付宝小程序不支持 in(component),使用无效果
4.2.1 SelectorQuery

查询节点信息的对象

selectorQuery.in(component)

将选择器的选取范围更改为自定义组件 component 内,返回一个 SelectorQuery 对象实例。(初始时,选择器仅选取页面范围的节点,不会选取任何自定义组件中的节点)。

js代码示例, 选项式API

const query = uni.createSelectorQuery().in(this);
query
  .select("#id")
  .boundingClientRect((data) => {
    console.log("得到布局位置信息" + JSON.stringify(data));
    console.log("节点离页面顶部的距离为" + data.top);
  })
  .exec();

注意: 支付宝小程序不支持 in(component),使用无效果

4.2.2 selectorQuery.select(selector)

在当前页面下选择第一个匹配选择器 selector 的节点,返回一个 NodesRef 对象实例,可以用于获取节点信息。

selector 说明:

selector 类似于 CSS 的选择器,但仅支持下列语法。

  • ID 选择器:#the-id
  • class 选择器(可以连续指定多个):.a-class.another-class
  • 子元素选择器:.the-parent > .the-child
  • 后代选择器:.the-ancestor .the-descendant
  • 跨自定义组件的后代选择器:.the-ancestor >>> .the-descendant (H5 暂不支持)
  • 多选择器的并集:#a-node, .some-other-nodes
4.3 vue的nextTick()函数

等待下一次 DOM 更新刷新的工具方法。

类型

function nextTick(callback?: () => void): Promise<void>

当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。

nextTick() 可以在状态改变后立即使用,以等待 DOM 更新完成。你可以传递一个回调函数作为参数,或者 await 返回的 Promise。

五、实例效果图

  1. 描述文案少于4行的UI效果图
    在这里插入图片描述
  1. 描述文案超过4行,超过的文案隐藏UI效果图
    在这里插入图片描述
  1. 描述文案全部显示UI效果图
    在这里插入图片描述

实例效果图演示步骤如下:

  1. 首先进入描述文案少于4行的UI效果图
  2. 点击更新描述文案,将描述文案增多,即可看到 描述文案超过4行,超过的文案隐藏UI效果图
  3. 点击查看详情按钮,即可查看完整的描述文案,同时查看详情按钮变成收起按钮,点击收起按钮即可隐藏超出的描述文案,这样也实现了展示全部描述文案和隐藏描述文案自由切换的功能,提升用户体验。

六、实例代码

testUniCreateSelectorQuery.vue文件代码

<template>
    <view class="content-root">
        <view class="content-wrap">
            <text class="title">{{ obj.title }}</text>
            <view id="desc" class="desc" :style="{ maxHeight: `${descViewHeight}rpx` }">
                <text>{{ obj.desc }}</text>
            </view>
            <view class="view-btn-wrap">
                <view class="view-btn" @click="updatDesc">更新描述文案</view>
                <view class="view-btn" v-if="showDetailBtn" @click="showOrHiddenDesc">{{ showOrHiddenDescBtnText }}
                </view>
            </view>
        </view>
    </view>
</template>
<script>
export default {
    data() {
        return {
            descViewHeight: 150,
            showOrHiddenDescBtnText: "查看详情",
            obj: {
                title: "1024程序员",
                desc: "1024程序员节(1024 Programmer's Day)是广大程序员的共同节日"
            },
            showDetailBtn: false,

        }
    },
    watch: {
        obj: {
            handler(newVal, oldVal) {
                console.log(`watch handler 回调 oldVal: ${oldVal} , newVal: ${newVal}`)
                this.trackObjDesc()
            },
            // 在侦听器创建时立即触发回调。第一次调用时,旧值将为 undefined。
            immediate: true,
            // 该回调将会在被侦听的对象的属性改变时调动,无论其被嵌套多深
            deep: true
        }
    },

    methods: {
        showOrHiddenDesc() {
            if (this.descViewHeight <= 150) {
                this.descViewHeight = 600
                this.showOrHiddenDescBtnText = "收起"
                return
            }

            this.descViewHeight = 150
            this.showOrHiddenDescBtnText = "查看详情"

        },
        // 更新desc内容
        updatDesc() {
            this.obj.desc = "1024程序员节(1024 Programmer's Day)是广大程序员的共同节日。1024是2的十次方,二进制计数的基本计量单位之一。针对程序员经常周末加班与工作日熬夜的情况,部分互联网机构倡议每年的10月24日为1024程序员节,在这一天建议程序员拒绝加班。\n 程序员就像是一个个1024,以最低调、踏实、核心的功能模块搭建起这个科技世界。1G=1024M,而1G与1级谐音,也有一级棒的意思。"
        },

        trackObjDesc() {
            let _this = this
            // nextTick() 可以在状态改变后立即使用,以等待 DOM 更新完成
            this.$nextTick(() => {
                // 使用 uni.createSelectorQuery() 需要在生命周期 mounted 后进行调用。
                // 默认需要使用到 selectorQuery.in 方法。
                const query = uni.createSelectorQuery().in(this);
                query.select("#desc").boundingClientRect(res => {
                    const height = res.height
                    _this.showDetailBtn = height > 75 ? true : false
                    console.log(`获取到 height = ${height} , _this.showDetailBtn = ${_this.showDetailBtn}`)
                }).exec();

            });
        }
    }
}
</script>
<style scoped>
.content-root {
    height: 100%;
    background-color: #ffffff;
    padding: 32rpx;
}

.content-wrap {
    background-color: #ffffff;
}

.title {
    color: #000;
    font-size: 28rpx;
    font-family: PingFangSC-Medium, PingFang SC;
    font-weight: bold;
}

.desc {
    /* max-height: 150rpx; */
    overflow: hidden;
    color: #000;
    font-size: 24rpx;
    padding: 16rpx;
    background-color: #f5f5f5;
    /*  text 组件包含了长文本,可以考虑增加 user-select 属性,方便用户复制。 */
    -webkit-user-select: text;
    /* Safari */
    -moz-user-select: text;
    /* Firefox */
    -ms-user-select: text;
    /* IE 10+ */
    user-select: text;
    /* Standard syntax */
}

.view-btn-wrap {
    display: flex;
    flex-direction: row;
    justify-content: space-between;
}

.view-btn {
    width: 200rpx;
    height: 80rpx;
    margin-top: 40rpx;
    line-height: 80rpx;
    align-items: center;
    text-align: center;
    border-radius: 8rpx;
    color: #000;
    font-size: 28rpx;
    background-color: #eeeeee;
}
</style>

更多推荐