Vue3 瀑布流 动态加载图片,下拉无限滚动

前置知识:

javascript获取屏幕高度和宽度、监听屏幕大小改变
https://blog.csdn.net/u014651560/article/details/107027414

js监听页面或元素scroll事件,滚动到底部或顶部
https://blog.csdn.net/mouday/article/details/125444003

JavaScript中获取DOM元素宽度和高度的常用API
https://juejin.cn/post/7011062379546935303

js中的各种高度(宽度)
https://juejin.cn/post/7017727173528125453

js 获取元素距离顶部高度
https://juejin.cn/s/js%20%E8%8E%B7%E5%8F%96%E5%85%83%E7%B4%A0%E8%B7%9D%E7%A6%BB%E9%A1%B6%E9%83%A8%E9%AB%98%E5%BA%A6

实现参考

waterfall瀑布流布局+动态渲染
https://juejin.cn/post/6902733543530184717
https://vuejs.org/guide/best-practices/performance.html
瀑布流的虚拟列表
https://juejin.cn/post/7002803053246185479
瀑布流使用虚拟列表性能优化
https://juejin.cn/post/6902733543530184717

实现思路

1、对元素进行按列区分
2、对元素进行从上到下逐个填充,优先填充高度最短的那一列
3、监听滚动事件,重新计算组件可见区域的高度,推算出需要填充多少才能铺满高度
4、功能扩充,预先加载等同于多铺一些高度,动态变更列数等同于重新铺图

关键变量获取:

 // 获取列表起始位置,用于计算列表在视窗内的位置
 let el = document.getElementById(uuid)
 let startTopOffset = el.offsetTop + el.offsetParent.offsetTop

 // 获取浏览器高度,用于计算可以显示的区域大小
 let viewHeight = document.documentElement.clientHeight

 // 添加可滚动的高度 - 当前滚动条滚动到哪里了
 let scrollOffset = document.documentElement.scrollTop
 
 // 当前组件顶部到可视区域结尾 需要填充的高度
 let comFillContentHeight = viewHeight - topOffset + scrollOffset;

完整代码:

<!-- 瀑布流 -->
<template>
    <div :id="uuid" style="width: 100%;">
        <!-- 瀑布流 -->
        <div class="box-wrapper-0">
            <div class="box-wrapper">
                <div class="box-col" v-for="col in waterFlowData">
                    <div class="box-item" v-for="(item, i) in  col">
                        <ImgShow :path="item.fullpath"></ImgShow>
                    </div>

                </div>
            </div>
        </div>
    </div>
</template>



<script setup>
import { ref, watch, computed, onMounted, reactive, defineAsyncComponent, toRaw } from 'vue'
import { FlushHistoryImages, HistoryGenImageInfoList } from '@/assets/GlobalStatus.js'
import api from './../assets/request_api'
import utils from '@/assets/utils.js'

const ImgShow = defineAsyncComponent({
    loader: () => import('../components/ImgShow.vue')

})

const props = defineProps(['max_count'])

let waterFlowData = ref([[{ fullpath: '' }]])
window.waterFlowData = waterFlowData

function onDownloadImg(url) {
    api.downloadImage(url, 'download');
}

function getElWidth(gap, colCount) {

    const el_width = (document.body.clientWidth - (colCount - 1) * gap) / colCount
    return el_width
}

let uuid = utils.uuid()
onMounted(() => {
    let loadState = false

    // 获取列表起始位置,用于计算列表在视窗内的位置
    let el = document.getElementById(uuid)
    let startTopOffset = el.offsetTop + el.offsetParent.offsetTop

    // 获取浏览器高度,用于计算可以显示的区域大小
    let viewHeight = document.documentElement.clientHeight

    // 添加可滚动的高度 - 当前滚动条滚动到哪里了
    let scrollOffset = document.documentElement.scrollTop

    let topOffset = 0;
    // 计算出当前组件可以显示的高度
    if (scrollOffset < startTopOffset) // 没有完全滚动到组件位置 可见区域要减去 startTopOffset
    {
        topOffset = startTopOffset - scrollOffset;
    } else {
        topOffset = 0;
    }

    // 可见区域大小
    let thatViewHeight = viewHeight - topOffset;
    // 当前组件顶部到可视区域结尾 需要填充的高度
    let comFillContentHeight = viewHeight - topOffset + scrollOffset;



    const gap = 15 // 缝隙的宽度

    const colCount = 4

    // let rowCount = (arrLen / colCount).toFixed(0)
    // let divSubCount = arrLen % colCount // 余数
    let idx = 0
    for (let c = 0; c < colCount; c++) {
        waterFlowData.value[c] = []
    }
    // 计算一个元素的宽度
    const elWidth = getElWidth(gap, colCount)

    const elHeights = [] // 每一列的高度
    for (let i = 0; i < colCount; i++) elHeights[i] = 0

    function getHeight(data, elWidth) { // 从图片数据中获取图片的高度信息,宽度信息
        let path = data.fullpath
        let t = path.substring(path.lastIndexOf('_') + 1, path.lastIndexOf('.')).split('x')
        let width = t[0]
        let height = t[1]

        // 由于元素会被缩放,所以实际高度也会缩放
        let elHeight = elWidth / width * height
        return elHeight
    }
    let arr = HistoryGenImageInfoList.value
    let arrLen = arr.length


    // 1、先填充第一排的元素
    function addToCol(colIdx, data) {
        let height = getHeight(data, elWidth);
        elHeights[colIdx] += height
        waterFlowData.value[colIdx].push(data)
    }

    function getLessHeightColIdx() {
        let t = 0;
        let tHeight = elHeights[0]
        for (let index = 0; index < colCount; index++) {
            if (elHeights[index] < tHeight) {
                t = index
                tHeight = elHeights[index]
            }
        }
        return { idx: t, height: tHeight }
    }

    function fillHeight(maxHeight) { // 一直填充到某个高度
        // 不断找最短的那一列进行添加 --- 直到填充完成
        for (; idx < arrLen; idx++) {
            let colIdx = getLessHeightColIdx()
            if (colIdx.height > maxHeight) break;
            console.log(colIdx.height);
            let data = arr[idx]
            addToCol(colIdx.idx, data)
        }
    }


    FlushHistoryImages(() => {
        arr = HistoryGenImageInfoList.value
        arrLen = arr.length

        for (let c = 0; c < colCount; c++) {
            if (idx >= arrLen) break;
            addToCol(c, arr[idx])
            idx++
        }

        loadState = true

        fillHeight(comFillContentHeight)
        // TODO 如何动态加载实现

        // 1、向下滚动时需要更新
        // 2、网页窗口变动时
        // 3、刷新网页时
        console.log('刷新, idx:' + idx + ' len:' + arrLen);
    })


    // 浏览器大小变更事件进行监听
    window.onresize = function () {
        viewHeight = document.documentElement.clientHeight;
        thatViewHeight = viewHeight - topOffset;
        comFillContentHeight = viewHeight - topOffset + scrollOffset;
        if (!loadState) return
        fillHeight(comFillContentHeight)
    }
    window.addEventListener('scroll', () => {
        scrollOffset = document.documentElement.scrollTop
        // 计算出当前组件可以显示的高度
        if (scrollOffset < startTopOffset) // 没有完全滚动到组件位置 可见区域要减去 startTopOffset
        {
            topOffset = startTopOffset - scrollOffset;
        } else {
            topOffset = 0;
        }
        // 可见区域大小
        thatViewHeight = viewHeight - topOffset;
        comFillContentHeight = viewHeight - topOffset + scrollOffset;

        if (!loadState) return
        fillHeight(comFillContentHeight)
    });

})


function log(obj) {
    if (!obj) {
        console.log(obj);
        return
    }
    console.log(obj);
    console.log(toRaw(obj).fullpath);
}

</script>






<style>
/* 瀑布流布局 */
.box-wrapper-0 {
    width: 100%;
}

.box-wrapper {
    width: 100%;
    gap: 15px;
    display: flex;
    box-sizing: border-box;
}

.box-col {
    width: 25%;
    gap: 1rem;
    box-sizing: border-box;
    display: flex;
    flex-direction: column;
}

.box-item>div {
    /* min-height: 110px; */
}

.box-col img {
    border-radius: 1rem;

    cursor: pointer;
    width: 100%;
    position: relative;
}
</style>

你可能感兴趣的:(vue,瀑布流)