vue自定义事件的疑问?元素进入可视区
各位大佬,对于vue中渲染列表时,我想知道如何比较友好的产生“元素进入可视区事件”?
举例说明:
如图所示,绿色中框为用户可视区(ul),9个蓝色框分别为信息条(li)。
可视区限制一定高度,产生纵向滚动条。2-7元素在可视区中,认为用户已读。1元素在上方,则忽略即认为已读。8、9在下方,还未出现,则认为未读。
我想知道,有没有什么事件可以绑定在li上,当8、9滑动到可视区时自动产生事件将其更为已读。
目前只能想到两个方法。
其一:
<ul class="messages"><li v-for="message in messages" v-bind:id="message.id"><li>
</ul>
<script>
export default {
name: 'App',
data(){
messages: []
},
methods:{
onScroll(){
let range = [$(".messages").offset().top,$(".messages").offset().top+$(".messages").height()];
$('.messages li').each(function(){
// 判断元素进入可视区
if($(this).offset().top >= range[0] && $(this).offset().top <= range[1]){
let id = $(this).prop('id');
for(let i in this.messages){
if(this.messages[i].id == id){
this.messages[i].status = 'read';
}
}
}
});
}
}
}
//
</script>
简单说说,这个方法,外露一个id属性,给js去遍历,然后再更新到data。
因为需要外露一个属性,感觉不是很好。
其二:
这个可以隐藏id,但是感觉也不是很好。
<ul class="messages"><li v-for="message in messages" v-bind:id="message.id" @click="message.status='read'"><li>
</ul>
<script>
export default {
name: 'App',
data(){
messages: []
},
methods:{
onScroll(){
let range = [$(".messages").offset().top,$(".messages").offset().top+$(".messages").height()];
$('.messages li').each(function(){
// 判断元素进入可视区
if($(this).offset().top >= range[0] && $(this).offset().top <= range[1]){
$(this).click();
}
});
}
}
}
//
</script>
此方法根据方法一稍微改改,通过click事件触发已读功能。(本想用焦点进入、其他事件、自定义事件结果都不好使)
我觉得这个相比比方法一好,目前也在用。缺点是如果li如果有其他事件,就需要混写在一起,感觉不好。
其次是,滚动事件每一次都要全量foreach一遍页面元素,感觉这个不是很妙。
各位sf的大佬,有没有好的解决方案呢?
我搜索时还发现一个“IntersectionObserver
”相关的,由于不是专业前端,不太清楚这个也没细研究。
回答
使用 Intersection Observer
,我以前写过一篇博客,可以看一下:《Intersection Observer 笔记》。简单来说,就是浏览器提供了一个原生 API 可以监控一个 DOM 的显示/隐藏,及百分比,接下来我们就可以组合使用,实现一些功能。写成代码大概是这样:
// 声明一个实例// 因为我的视口即当前 viewport,所以这里不需要 `options`
const observer = new IntersectionObserver(entries => {
// 遍历所有实例,如果它显示出来,即 intersectionRatio 显示比例大于 0
// 那么就让它 `dispatch('visible')`
entries.forEach(({target, intersectionRatio}) => {
const event = new CustomEvent('visible', {
detail: {
isVisible: intersectionRatio > 0,
},
});
target.dispatchEvent(event);
});
});
// 然后可以在 Vue 里侦听这个事件
export default {
template: '<div @visible="onVisible"></div>';
}
getBoundingClientRect用于获取某个元素相对于视窗的位置集合。集合中有top, right, bottom, left等属性。可以查一下mdn
直接用ref取就可以
<template> <div class="container">
<ul ref="ul">
<li v-for="item in messages" :key="item.id" :ref="item.id">
<div>{{ item.status }}</div>
{{ item.id }}
</li>
</ul>
<button @click="viewStatus">查看信息状态</button>
</div>
</template>
<script>
export default {
data() {
return {
messages: [],
unreadIndex: 0, //未读起始索引
ulHeight: 0,
};
},
components: {},
computed: {},
created() {
for (let i = 0; i < 9; i++) {
this.messages.push({
id: i + 1,
status: "未读",
});
}
},
mounted() {
this.ulHeight = this.$refs.ul.offsetHeight;
this.checkStatus(this.unreadIndex);
this.$refs.ul.onscroll = (e) => {
this.checkStatus(this.unreadIndex);
};
},
methods: {
viewStatus() {
console.log("查看所有信息状态:");
for (let msg of this.messages) {
console.log(`${msg.id}:${msg.status}`);
}
},
checkStatus(index) {
for (let i = index; i < this.messages.length; i++) {
let msgOffsetop =
this.$refs[this.messages[i].id][0].offsetTop -
this.$refs.ul.scrollTop;
if (msgOffsetop < this.ulHeight) {
//进入过可视区的
this.$set(this.messages, i, {
id: this.messages[i].id,
status: "已读",
});
} else {
this.unreadIndex = i;
break;
}
}
},
},
};
</script>
<style lang="less" scoped>
.container {
text-align: center;
ul {
position: relative;
list-style: none;
padding: 0;
width: 200px;
height: 300px;
margin: 50px auto;
border: 1px solid green;
overflow: auto;
li {
position: relative;
padding: 20px 0;
&:nth-child(odd) {
background: #e3f9fd;
}
& > div {
position: absolute;
right: 5px;
}
}
}
}
</style>
使用 getBoundingClientRect
而不是 Intersection Observer
, getBoundingClientRect
有更好的兼容性。
/** * @description 获取元素相对与浏览器视口的位置
* @param {Object} client document对象
* @returns top, bottom, left, right, height, width
*/
function getClientRect(el) {
const {
top,
bottom,
left,
right,
height,
width,
} = el.getBoundingClientRect()
return {
top,
bottom,
left,
right,
height: height || bottom - top,
width: width || right - left,
}
}
以上是 vue自定义事件的疑问?元素进入可视区 的全部内容, 来源链接: utcz.com/a/28277.html