提醒
本文实例是使用uniapp进行开发演示的。
一、需求场景
在开发详情页面时,不同产品描述文案不同,有的文案比较长,需求上要求描述文案最多展示4行文案,少于4行文案,全部显示,此UI高度自动适配,例如,只有一行文案,那么UI高度也是展示一行的高度,超过的部分文案不显示同时右边显示查看详情、查看更多或者展开等按钮,让用户点击此按钮进行查看。
二、需求分析
- 详情页面是通用的,显示描述的UI的高度是可变化的,最多是4行文案的高度;
- 实时监听描述文案的属性变化;
- 实时更新UI高度以及对应的UI样式;
- 需要知道什么时候DOM更新完成的;
三、技术方案
- 设置css属性:
max-height: xxx; overflow: hidden;
控制描述文案UI的最大高度和超过的文案隐藏起来;- 使用vue的watch函数监听描述文案的属性变化;
- 使用uni.createSelectorQuery()获取节点信息(描述文案UI的高度);
- 使用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。
五、实例效果图
- 描述文案少于4行的UI效果图
- 描述文案超过4行,超过的文案隐藏UI效果图
- 描述文案全部显示UI效果图
实例效果图演示步骤如下:
- 首先进入描述文案少于4行的UI效果图
- 点击更新描述文案,将描述文案增多,即可看到 描述文案超过4行,超过的文案隐藏UI效果图
- 点击查看详情按钮,即可查看完整的描述文案,同时查看详情按钮变成收起按钮,点击收起按钮即可隐藏超出的描述文案,这样也实现了展示全部描述文案和隐藏描述文案自由切换的功能,提升用户体验。
六、实例代码
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>