vue3实现一个滚动分页加载瀑布流列表过程

 更新时间:2026年02月10日 08:53:25   作者:爱拍照的小码农  
文章介绍了如何使用Vue3和JavaScript实现一个滚动分页加载的瀑布流列表项目框架,主要选择了vue-waterfall-next插件,并结合原生JavaScript实现了滚动加载功能,包括防抖处理以优化页面性能

实现一个滚动分页加载瀑布流列表

项目框架:vue3+JS+vite

瀑布流框架:vue-waterfall-next

问了chatGPT,对于vue3有哪些好用的瀑布流插件,回答如下:

对于 Vue 3,有一些瀑布流插件可供选择。以下是几个常用的 Vue 3 瀑布流插件:

  • vue-waterfall-next:这是一个基于 Vue 3 的瀑布流插件,它提供了简单易用的 API,可以帮助你实现瀑布流布局。它支持动态加载数据和无限滚动,并且具有自定义配置选项,如列数、间距等。
  • vue-masonry-wall:这是一个基于 Vue 3 的瀑布流插件,它使用 Masonry 布局算法来实现瀑布流效果。它支持自适应布局和动态加载数据,并且可以根据需要进行自定义样式。
  • vue-infinite-scroll:虽然不是专门针对瀑布流设计的插件,但它可以与瀑布流布局结合使用,实现无限滚动加载。它支持 Vue 3,并提供了简单的指令来处理滚动加载的逻辑。

这些插件都可以通过 npm 安装,并且有详细的文档和示例代码,你可以根据自己的需求选择合适的插件进行使用。记得检查插件的版本和兼容性,以确保与你的 Vue 3 项目兼容。

结合项目实际,最后选择用vue-waterfall-next,

插件可在github上找到,用法如下:

安装

npm install vue-waterfall-plugin-next

使用

在vue项目中引入:

import { LazyImg, Waterfall } from ‘vue-waterfall-plugin-next'
import ‘vue-waterfall-plugin-next/dist/style.css'

由于该插件只能实现瀑布流布局,不能实现滚动分页加载,而vant的无限滚动功能在此使用会有问题,所以无限滚动这块用的是原生js去实现

具体代码

如下所示:

// 父组件index.vue
<template>
	<div class="container" id="main">
    	<productCard :productList="productList"></productCard>
       	 <div class="loading-text" v-if="loading">加载中...</div>
         <div class="loading-text" v-if="finish">没有更多了</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([])
        
        //获取接口数据
        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)
            //滚动条滚动距离
            const scrollTop = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop
            //窗口可视范围高度
            const clientHeight =
                window.innerHeight || Math.min(document.documentElement.clientHeight, document.body.clientHeight)

            if (clientHeight + scrollTop >= scrollHeight  && page.value <= total.value) {
                //快到底时----加载
                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>

// 子组件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 && !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: {
                //当屏幕宽度小于等于1200
                rowPerView: 4,
            },
            800: {
                //当屏幕宽度小于等于800
                rowPerView: 3,
            },
            500: {
                //当屏幕宽度小于等于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;
        &.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);
                &:last-child {
                    margin-right: 0;
                }
            }
        }
    }
}
</style>

测试的时候发现滚动的太快页面会出现抖动现象,所以在监听页面滚动这里需要加一个防抖,代码如下:

//防抖函数
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))
})

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 详解VUE中的Proxy代理

    详解VUE中的Proxy代理

    这篇文章主要介绍了Proxy代理对象详解,Proxy 是ES6中提供的一个非常强大的功能,可以用来代理另一个对象,从而拦截、监视并修改这个对象的各种操作,需要的朋友可以参考下
    2023-04-04
  • vue3脚手架简单静态路由解读

    vue3脚手架简单静态路由解读

    这篇文章主要介绍了vue3脚手架简单静态路由,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教
    2023-10-10
  • vue-cli axios请求方式及跨域处理问题

    vue-cli axios请求方式及跨域处理问题

    这篇文章主要介绍了vue-cli axios请求方式及跨域处理问题,文中还给大家提到了vue中axios解决跨域问题和拦截器使用,非常不错,具有参考借鉴价值,需要的朋友参考下吧
    2018-03-03
  • Vue分页组件实例代码

    Vue分页组件实例代码

    这篇文章主要为大家详细介绍了Vue分页组件的实例代码,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2017-04-04
  • proxy代理不生效以及vue config.js不生效解决方法

    proxy代理不生效以及vue config.js不生效解决方法

    在开发Vue项目过程中,使用了Proxy代理进行数据劫持,但是在实际运行过程中发现代理并没有生效,也就是说数据并没有被劫持,这篇文章主要给大家介绍了关于proxy代理不生效以及vue config.js不生效解决方法的相关资料,需要的朋友可以参考下
    2023-11-11
  • 教你60行代码实现一个迷你响应式系统vue

    教你60行代码实现一个迷你响应式系统vue

    这篇文章主要为大家介绍了教你60行代码实现一个迷你响应式系统详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪<BR>
    2023-03-03
  • 关于antd中select搜索框改变搜索值的问题

    关于antd中select搜索框改变搜索值的问题

    这篇文章主要介绍了关于antd中select搜索框改变搜索值的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-04-04
  • vue、react等单页面项目部署到服务器的方法及vue和react的区别

    vue、react等单页面项目部署到服务器的方法及vue和react的区别

    这篇文章主要介绍了vue、react等单页面项目部署到服务器的方法,需要的朋友可以参考下
    2018-09-09
  • Vue浅析axios二次封装与节流及防抖的实现

    Vue浅析axios二次封装与节流及防抖的实现

    axios是基于promise的HTTP库,可以使用在浏览器和node.js中,它不是vue的第三方插件,vue-axios是axios集成到Vue.js的小包装器,可以像插件一样安装使用:Vue.use(VueAxios, axios),本文给大家介绍axios的二次封装和节流与防抖
    2022-08-08
  • 浅析Vue.js中v-bind v-model的使用和区别

    浅析Vue.js中v-bind v-model的使用和区别

    v-model 指令在表单控件元素上创建双向数据绑定,所谓双向绑定。这篇文章主要介绍了Vue.js中v-bind v-model的使用和区别,需要的朋友可以参考下
    2018-12-12

最新评论