vue3 implements a scrolling paging loading waterfall flow list

Project framework: vue3 + JS + vite
Waterfall framework: vue-waterfall-next

Asked chatGPT about useful waterfall flow plug-ins for vue3, and the answer was as follows:
For Vue 3, there are a few waterfall plugins available. The following are several commonly used Vue 3 waterfall plug-ins:
vue-waterfall-next: This is a waterfall flow plug-in based on Vue 3. It provides a simple and easy-to-use API that can help you implement waterfall flow layout. It supports dynamic loading of data and infinite scrolling, and has custom configuration options such as number of columns, spacing, etc.
vue-masonry-wall: This is a waterfall flow plug-in based on Vue 3, which uses the Masonry layout algorithm to achieve the waterfall flow effect. It supports adaptive layout and dynamic loading of data, and can be customized as needed.
vue-infinite-scroll: Although it is not a plug-in specifically designed for waterfall flow, it can be used in conjunction with waterfall flow layout to achieve infinite scroll loading. It supports Vue 3 and provides simple directives to handle rolling loading logic.
These plug-ins can be installed through npm and have detailed documentation and sample code. You can choose the appropriate plug-in to use according to your needs. Remember to check the plugin’s version and compatibility to ensure compatibility with your Vue 3 project.

Based on the actual project, I finally chose to use vue-waterfall-next.
The plug-in can be found on github and is used as follows:

Installation
npm install vue-waterfall-plugin-next
Use
Introduced in the vue project:
import { LazyImg, Waterfall } from vue-waterfall-plugin-next’
import vue-waterfall-plugin-next/dist/style.css’

Since this plug-in can only implement waterfall flow layout and cannot implement scrolling paging loading, and vant’s infinite scrolling function will have problems when used here, so the infinite scrolling is implemented using native js

The specific code is as follows:

// Parent component index.vue
<template>
<div class="container" id="main">
    <productCard :productList="productList"></productCard>
       <div class="loading-text" v-if="loading">Loading...</div>
         <div class="loading-text" v-if="finish">No more</div>
   </div>
</template>
<script>
import productCard from '@/components/productCard.vue'
import {<!-- --> getAllGoods } from '@/api/modules/good.js'
export default {<!-- -->
components: {<!-- -->
        productCard,
    },
     setup() {<!-- -->
const page = ref(0)
        const size = ref(8)
        const loading = ref(false)
        const finish = ref(false)
        const productList = ref([])
        
        //Get interface data
        const getProduct = () => {<!-- -->
            loading.value = true
            const params = {<!-- -->
                page: page.value,
                size: size.value,
                body: {<!-- -->
                    goodsTypeID: className,
                },
            }
            getAllGoods(params)
                .then(res => {<!-- -->
                    if (res.code === 20000) {<!-- -->
                        total.value = Number(res.data.totalPages)
                        if (res.data.list.length > 0) {<!-- -->
                            productList.value = [...productList.value, ...res.data.list]
                        }
                        if (page.value == total.value + 1) {<!-- -->
                            finish.value = true
                            loading.value = false
                        }
                    } else {<!-- -->
                        loading.value = false
                        finish.value = true
                    }
                })
                .catch(err => {<!-- -->
                    loading.value = false
                    finish.value = true
                })
        }
        const handleScroll = () => {<!-- -->
            const scrollHeight = Math.max(document.documentElement.scrollHeight, document.body.scrollHeight)
            //Scroll bar scrolling distance
            const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
            //Window visible range height
            const clientHeight =
                window.innerHeight || Math.min(document.documentElement.clientHeight, document.body.clientHeight)

            if (clientHeight + scrollTop >= scrollHeight & amp; & amp; page.value <= total.value) {<!-- -->
                //Nearly to the end----Loading
                page.value++
                getProduct()
            }
        }
        onMounted(() => {<!-- -->
            getProduct(tabModule.activeType.value, tabModule.activeClass.value)
            window.addEventListener('scroll', handleScroll)
        })
        onUnmounted(() => {<!-- -->
            window.removeEventListener('scroll', handleScroll)
        })
        return {<!-- -->
            productList,
            loading,
            finish,
        }
}
}
</script>

<style lang="scss" scoped>
.loading-text {<!-- -->
text-align: center;
position: absolute;
left: 0;
right: 0;
z-index: 999;
margin: auto;
padding: 20px 0;
font-size: 16px;
}
:deep(.waterfall-list) {<!-- -->
background: none;
}
.container {<!-- -->
        padding: 0 12px;
      }
</style>

//Subcomponent productCard.vue
<template>
    <Waterfall :lazyload="false" :breakpoints="breakpoints" :gutter="8" :list="list">
        <template #item="{ item, url, index }">
            <div class="card_content">
                <div class="card_img" :class="{ active: !item.goodsPicture & amp; & amp; !item.storePicture }">
                    <LazyImg class="cover" :url="item.goodsPicture || item.storePicture || item.storeLogo" />
                </div>
                <div class="content">
                    <div class="store" v-if="item.type === 2">{<!-- -->{ item.storeName }}</div>
                    <div class="title" v-if="item.type === 1">{<!-- -->{ item.storeName }}</div>
                    <div class="title" v-if="item.type === 2">{<!-- -->{ item.goodsName }}</div>
                    <div class="tags">
                        <div class="tags-item" v-for="(ele, index) in item.tags" :key="index">
                            {<!-- -->{ ele }}
                        </div>
                    </div>
                </div>
            </div>
        </template>
    </Waterfall>
</template>

<script>
import {<!-- --> computed, ref } from 'vue'
import {<!-- --> LazyImg, Waterfall } from 'vue-waterfall-plugin-next'
import 'vue-waterfall-plugin-next/dist/style.css'
export default {<!-- -->
    props: {<!-- -->
        productList: Array,
    },
    components: {<!-- -->
        LazyImg,
        Waterfall,
    },
    setup(props) {<!-- -->
        const list = computed(() => {<!-- -->
            return props.productList
        })
        const breakpoints = ref({<!-- -->
            1200: {<!-- -->
                //When the screen width is less than or equal to 1200
                rowPerView: 4,
            },
            800: {<!-- -->
                //When the screen width is less than or equal to 800
                rowPerView: 3,
            },
            500: {<!-- -->
                //When the screen width is less than or equal to 500
                rowPerView: 2,
            },
        })

        return {<!-- -->
            breakpoints,
            list,
        }
    },
}
</script>

<style lang="scss" scoped>
.card_content {<!-- -->
    border-radius: 4px;
    background: #fff;
    box-sizing: border-box;
    .card_img {<!-- -->
        margin-bottom: 7px;
         & amp;.active {<!-- -->
            border: 1px solid #e7e7e7;
        }
        :deep(.lazy__img) {<!-- -->
            width: 100%;
            border-radius: 4px;
            font-size: 0;
            height: 100%;
        }
    }
    .content {<!-- -->
        padding: 0 8px;
        .store {<!-- -->
            color: rgba(0, 0, 0, 0.4);
            font-size: 12px;
            font-weight: 400;
            margin-bottom: 4px;
        }
        .title {<!-- -->
            font-size: 16px;
            font-weight: 500;
            margin-bottom: 14px;
        }
        .tags {<!-- -->
            display: flex;
            flex-wrap: wrap;
            .tags-item {<!-- -->
                background: rgba(153, 151, 255, 0.05);
                border-radius: 2px;
                padding: 3px 4px;
                margin: 0 5px 5px 0;
                color: rgba(0, 0, 0, 0.4);
                font-size: 12px;
                border: 1px solid rgb(244, 244, 249);
                 & amp;:last-child {<!-- -->
                    margin-right: 0;
                }
            }
        }
    }
}
</style>

During the test, it was found that the page would shake if you scrolled too fast, so you need to add an anti-shake when monitoring the scrolling of the page. The code is as follows:

//Anti-shake function
const debounce = (fn, delay) => {<!-- -->
    let timeout
    return function () {<!-- -->
        clearTimeout(timeout)
        timeout = setTimeout(() => {<!-- -->
            fn.apply(this, arguments)
        }, delay)
    }
}
onMounted(() => {<!-- -->
   getProduct()
   window.addEventListener('scroll', debounce(handleScroll, 200))
})