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

回到顶部