77

使用Intersection Observer API构建无限滚动组件

 6 years ago
source link: https://www.w3cplus.com/vue/build-an-infinite-scroll-component-using-intersection-observer-api.html?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

特别声明:本文根据@Alex Jover Morales的《 Build an Infinite Scroll component using Intersection Observer API 》一文所整理。

在开发过程中,经常会遇到要处理大量数据的情况,比如列表、搜索记录等,因此你需要一种方法让用户以块状显示这些数据,以便保持应用程序性能和数据的有序性。

你可能会使用分页组件来处理,它可以轻松跳转,甚至一次跳转几个页面。

当然,元素滚动是分页组件的另一种替代方案,它可以提供更好的用户体验,特别是在移动端和可触摸设备上。当用记在页面上滚动时,它提供了一个透明的分页,给人一种没有结尾列表的感觉。

自从 Intersection Observer API 出现之后,构建无限滚动组件变得更简单。让我们看看如何通过这个API来构建无限滚动组件。

Intersection Observer API

Intersection Observer API提供了一个可订阅的模型,可以观察该模型,以便在元素进入视窗时得到通知。

创建一个观察者实例很简单,我们只需要创建一个 IntersectionObserve 的新实例并调用 observe 方法,传递一个DOM元素:

const observer = new IntersectionObserver()

const coolElement = document.querySelector('#coolElement')
observer.observe(coolElement)

但是,当观察者进入 coolElement 视窗时,我们如何得到通知呢? IntersectionObserver 构造函数接受一个回调作为参数,我们可以这样使用它:

const observer = new IntersectionObserver(entries => {
    const firstEntry = entries[0]

    if (firstEntry.isIntersecting) {
        // Handle intersection here...
    }
})

const coolDiv = document.querySelector('#coolDiv')
observer.observe(coolDiv)

如你所见,回调将接收 entries 作为其参数。它是一个数组,因为当你使用 阈值 时,你可以有几个 entries ,但事实并非如此,所以我们只会得到第一个元素。然后我们可以使用 firstEntry.isIntersection 属性来检查它是否相交。这是进行异步请求并检索下一个页面数据的好方法。

IntersectionObserver 构造函数使用下面的方法接受选项对象( options )为其第二个参数:

const options = {
    root: document.querySelector('#scrollArea')
    rootMargin: '0px'
    threshold: 1.0
}

const observer = new IntersectionObserver(callback, options)

rootMargin 对于我们的示例非常有用,因为它提供了一种定义 margin 的方法,观察者可以使用它来查找交集。默认情况下,它是 0 ,表示观察者一进入视窗就会触发交叉事件(Intersect Event)。但是设置一个 400pxrootMargin 意味着交叉回调将在元素进入视窗之前 400px 位置处触发。

因为 rootthreshold 对于这种呢况没有什么意义(因此超出了范围),有关于这方面可以 查阅文档 进行了解。

知道如何使用交点观察器,我们可以在列表末尾放置一个 Observable 组件,以便在用户到达列表底部时添加更多数据。

Observer组件

前面的例子很酷,对吧?但是对于我们来说有一个Vue组件是很方便的,所以我们可以在我们的Vue应用程序中使用它。

我们可以使用一个 mounted 钩子来创建我们需要保存在组件状态变量中的观察者。使用 mounted 钩子而不是 created 钩子很重要,因为我们需要一个DOM元素来观察,而在 created 钩子中我们没有它:

// Observer.vue
export default {
    data: () => ({
        observer: null
    }),

    mounted() {
        this.observer = new IntersectionObserver(([entry]) => {
            if (entry && entry.isIntersecting) {
                // ...
            }
        })

        this.observer.observe(this.$el)
    }
}

注意:我们在 [entry] 参数上使用数组解构。这是一种速记方式,相当于获取 entries 数组并将第一个元素作为 entries[0] 访问。

正如你所见,我们使用 this.$el 作为 root 元素以便观察DOM元素。

为了使其可重用,我们需要让父组件(使用 Observer 组件的组件)处理交叉的事件。为此,我们在它相交时发出一个自定义事件 intersect

export default {
    mounted() {
        this.observer = new intersectionObserver(([entry]) => {
            if (entry && entry.isIntersecting) {
                this.$emit('intersect')
            }
        })

        this.observer.observe(this.$el)
    }
    // ...
}

根据组件的模板,我们只需要任何元素,所以我们可以使用一个没有任何大小的 <div>

<template>
    <div class="observer"/>
</template>

最后,在组件被销毁时清理观察者很重要,否则,我们将在应用程序中会造成内存泄漏,因为事件监听器不会被清除。我们可以在 destroyed 钩子中来调用 observerdisconnect 方法:

export default {
    destroyed() {
        this.observer.disconnect()
    }
    // ...
}

你会发现还有 unobserve 方法。主要区别是:

unobserve
disconnect

在我们的示例中,因为我们只有一个元素,所以它们都可以工作。

我们还可以添加一个 options 属性,以便在需要使用 rootMargin 的情奖品下将 IntersectionObserver 选项传递给它。

将所有内容放在 Observer.vue 组件中:

<!-- Observer.vue -->
<template>
    <div class="observer" />
</template>

<script>
    export default {
        props: ['options'],
        data: () => ({
            observer: null
        }),
        mounted() {
            const options = this.options || {}
            this.observer = new IntersectionObserver(([entry]) => {
                if (entry && entry.isIntersecting) {
                    this.$emit('intersect')
                }
            }, options)

            this.observer.observe(this.$el)
        },
        destroyed() {
            this.observer.disconnect()
        }
    }
</script>

构建元限滚动

假设你有一个列表组件,类似以下内容:

<template>
    <div>
        <ul>
            <li class="list-item" v-for="item in items" :key="item.id">{{ item.name }}</li>
        </ul>
    </div>
</template>

<script>
    export default {
        data: () => ({
            items: []
        }),
        async mounted() {
            const res = await fetch('https://jsonplaceholder.typicode.com/comment')
            this.items = await res.json()
        }
    }
</script>

请注意,代码中使用了 asyncawait 语法,使异步代码看起来很漂亮。有关于这方面的信息可以阅读 这篇文章

这个组件使用 v-foritems 渲染到列表中。在 mounted 钩子中,它使用 Fetch API jsonplaceholder.typicode.com 中获取一些模拟数据,用于填充 items 变量。

添加分页

它可以正常工作,但没有分页功能。为此, jsonplaceholder.typicode.com 的端点允许我们使用 _page_limit 来控制返回的数据。此外,我们需要一个 page 变量来跟踪,它从 1 开始。

我来改变上面的代码,以便进行分页:

export default {
    data: () => ({
        page: 1,
        items: []
    }),
    async mounted() {
        const res = await fetch(
            `https://jsonplaceholder.typicode.com/comments?_page=${this.page}&_limit=50`
        )

        this.items = await res.json()
    }
}

现在我们有分页功能了,每个页限制 50 个元素。

添加Observer组件

我们仍然需要创建无限滚动组件,接下来把 Observer 组件引入进来。我们将在列表底部使用它,当它到达视窗时,它将获取下一页并增加该页。

首先,导入 Observer 组件,并将其添加到 InfiniteScroll 组件中:

<template>
    <div>
        <ul>
            <li class="list-item" v-for="item in items" :key="item.id">{{ item.name }}</li>
        </ul>
        <Observer @intersect="intersected" />
    </div>
</template>

<script>
    import Observer from './components/Observer'

    export default {
        // ...
        components: {
            Observer
        }
    }
</script>

最后,我们可以在 mounted 钩子上的代码移动 intersected 方法中。在 Observer 组件的 intersect 定制事件。

export default {
    data: () => ({
        page: 1,
        items: []
    }),
    methods: {
        async intersected() {
            const res = await fetch(`https://jsonplaceholder.typicode.com/comments?_page=${this.page}&_limit=50`)
        }

        this.page++

        const items = await res.json()
        this.items = [...this.items, ...items]
    }
}

请记信,我们必须增加页面。此外,现在我们必须将项添加到现有的 this.items 数组。我们通过在 this.items=[...this.items, ...items] 来实现。这基本上和老办法 this.items = this.items.concat(items) 是一样的。

无限滚动组件,全部代码看起来像下面这样:

<!-- InfiniteScroll.vue -->
<template>
    <div>
        <ul>
            <li class="list-item" v-for="item in items" :key="item.id">{{ item.name }}</li>
        </ul>
        <Observer @intersect="intersected" />
    </div>
</template>

<script>
    import Observer from './components/Observer'

    export default {
        data: () => ({
            page: 1,
            items: []
        }),
        methods: {
            async intersected() {
                const res = await fetch(`https://jsonplaceholder.typicode.com/comments?_page=${this.page}&_limit=50`)

                this.page++

                const items = await res.json()
                this.items = [...this.items, ...items]
            }
        },
        components: {
            Observer
        }
    }
</script>

总结

无限滚动组件是数据分页的一种很好的展示方式,特别是在移动设备和可触摸设备上。通过添加 IntersectionObserver API,它变得更加容易。在本文中,我们已经完成了自己构建一个无限滚动组件所有步骤。

请记住,如果你需要支持旧的浏览器,你可能需要 W3C的 IntersectionObserver Github上Fetch Polyfill

最终Demo效果如下:


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK