需求中有这样的一条:要将聊天消息定位到首个未读消息。看到这样的需求笔者灵机一动:首先想到就是把聊天消息放到滚动容器中,然后根据已读的消息设置滚动条的滚动高度,如此一来就可以定位到未读消息啦。
wx.createSelectorQuery、SelectorQuery 与 NodesRef
可是这个解决方案中的关键点时啥啊?想一想,无非就是要知道那些已读消息的高度。那么微信中有没有相关的 API 可以获取到 wxml 中元素(组件)的高度呢?还真有,那就是wx.createSelectorQuery。
wx.createSelectorQuery 方法调用后会返回一个 SelectorQuery 对象实例,SelectorQuery 对象实例用于查询节点信息的对象,提供了如下的方法(以下 5 个方法本文中都会使用,都需要关注! ):
- SelectorQuery SelectorQuery.in(Component component):将选择器的选取范围更改为自定义组件 component 内。
- NodesRef SelectorQuery.select(string selector):在当前页面下选择第一个匹配选择器 selector 的节点。返回一个 NodesRef 对象实例,可以用于获取节点信息。
- NodesRef SelectorQuery.selectAll(string selector):在当前页面下选择匹配选择器 selector 的所有节点。
- NodesRef SelectorQuery.selectViewport():选择显示区域。可用于获取显示区域的尺寸、滚动位置等信息。
- NodesRef SelectorQuery.exec(function callback):执行所有的请求。请求结果按请求次序构成数组,在 callback 的第一个参数中返回。
看到对于 select、selectAll、selectViewport 还有 exec 方法都返回NodesRef类型的对象,它是用于获取 WXML 节点信息的对象,看一下这个类的实例可以调用的一个重要的方法:
SelectorQuery NodesRef.boundingClientRect(NodesRef.boundingClientRectCallback callback):添加节点的布局位置的查询请求。相对于显示区域,以像素为单位。其功能类似于 DOM 的 getBoundingClientRect。返回 NodesRef 对应的 SelectorQuery 。
需要注意:SelectorQuery 实例的 select、selectAll、selectViewport 方法调用后会返回 NodesRef 的实例,NodesRef 实例的 boundingClientRect 方法调用后又会返回 SelectorQuery 实例。这也就是本文中接下来介绍的演示代码中可以链式调用的原因。
代码示例
如下所示:在 data 中定义了 scrollTop 用于控制 scroll-view 组件的滚动高度;定义了 blockList 数组用于循环渲染出 view 组件从而来模拟聊天消息,注意这里定义了 9 个已读消息,10 个未读消息。
data: {
scrollTop: 0,
blockList: [
{
content: '1',
hasRead: true
},
//....
{
content: '9',
hasRead: true
},
{
content: '10',
hasRead: false
},
//....
{
content: '20',
hasRead: false
},
]
},
如下所示的 wxml 代码中,在 scroll-view 组件中嵌套了一个固定的 view 组件和用 wx:for 指令循环生成的一组 view 组件。注意对于循环生成的 view 组件为其绑定了动态的 class,如果是已读消息则 class 类名为 read-class,如果是未读消息则 class 类名为 unread-class:
<!--pages/test/test.wxml-->
<view class="test-container">这是页面主体
<scroll-view class="test-scroll" scroll-top="{{scrollTop}}" scroll-y="true" bindscrolltolower="loadMore">
<view class="block-read" >我是测试内容块</view>
<view wx:for="{{blockList}}" class="{{item.hasRead ? 'read-class': 'unread-class'}}" >
{{item.content}}
</view>
</scroll-view>
</view>
如下所示的 css 代码中为固定的 view 组件定义了灰色(#ccc)的边框,为循环生成的代表已读消息的 view 组件定义了绿色边框,为循环生成的代表未读消息的 view 组件定义了红色边框:
.block-read {
height: 400rpx;
border: 1px solid #ccc;
}
.read-class {
height: 200rpx;
border: 1px solid green;
}
.unread-class {
height: 180rpx;
border: 1px solid red;
}
.test-scroll {
height: 700px;
}
以上代码的执行效果如下图所示:
下面尝试使用 wx.createSelectorQuery,SelectorQuery 类的 select、selectAll,exec 方法,NodesRef 的 boundingClientRect 方法来获取 view 组件的高度。
首先使用 select 选中未读消息,代码如下图所示:
getViewHeight() {
const query = wx.createSelectorQuery().in(this)
query.select('.read-class').boundingClientRect((res:any) => {
console.log(res)
}).exec()
},
由于 select 只会在当前页面下选择第一个匹配选择器的节点,所以可以打印出一个节点的信息,如下图所示:
下面代码中将 select 换成 selectAll:
getViewHeight() {
const query = wx.createSelectorQuery().in(this)
query.selectAll('.read-class').boundingClientRect((res:any) => {
console.log(res)
}).exec()
},
selectAll 可以在当前页面下选择匹配选择器的所有节点,所以可以打印出一个数组,如下图所示
有了所有节点的数组后就可以循环遍历数组元素对元素 height 值累加,这样就得到了所有已读消息的高度,滚动条的 scrollTop 赋值为这个值之后就可以定位到首个未读消息了:
getViewHeight() {
const query = wx.createSelectorQuery().in(this)
query.selectAll('.read-class').boundingClientRect((res:any) => {
let scrollTop = 0
res.forEach((item: any) => {
scrollTop += item.height
})
console.log(scrollTop)
this.setData({
scrollTop:scrollTop
})
}).exec()
},
onLoad() {
this.getViewHeight()
},
代码执行效果如下图所示:
最后再来看一下 selectViewport 的使用:
const query = wx.createSelectorQuery().in(this);
query
.selectViewport()
.boundingClientRect((res) => {
console.log(res);
})
.exec();
selectViewport 会选择显示区域,可用于获取显示区域的尺寸、滚动位置等信息,代码执行结果如下图所示:
可以看到显示区域的高度是 721px ,显示区域的宽度是 375px。使用调试器中的 wxml 进行元素选择得到的结果和代码是一致的,如下图所示:
总结
要将聊天消息定位到首个未读消息的需求可以把聊天消息放到滚动容器中,然后根据已读的消息设置滚动条的滚动高度。
scroll-view 组件的 scroll-top 属性用于设置竖向滚动条位置。
wx.createSelectorQuery 方法调用后会返回一个 SelectorQuery 对象实例,SelectorQuery 对象实例用于查询节点信息,提供了 select、selectAll、selectViewport 等方法。
NodesRef 类型的对象是用于获取 WXML 节点信息的对象,可以调用 boundingClientRect 来查询节点的布局位置。