Nuxt3 + Vue3 实现瀑布流 支持SSR

效果图

在这里插入图片描述

实现方法

方法特点优点缺点
column-count顺序只能是 从上到下, 再左到右纯css布局 代码少 实现快由于排列顺序是先 从上到下, 再左到右,只能用于数据固定, 无法动态的加载追加,对于滚动到底部加载新数据则无法实现。
flex同样相对简单纯css布局 代码少 实现快需要指定高度,并且在每一列放置合适数量的图片,如果没有给容器设置固定宽度,则当容器宽度缩小时,图片的宽度相应的缩小,由于图片的高度是auto,即按图片原比例展示,因此图片高度会相应减少,由于高度指定,图片数量固定,则可能会出现图片占不满列数的情况
定位可以实现分页加载分页加载相对来说比较复杂 每个div之间都需要计算

1.column-count 布局

.waterfall{
    column-count: 4;//想要排成的列数
    column-gap: 0;
	    .waterfall-item {
	    	width: 100%;
	    	// 有时候页面会出现在前几列的最后一个元素的内容被自动断开,一部分在当前列尾,一部分在下一列的列头。
	    	break-inside: avoid; // 不被截断 默认值是auto,会被截断
	    	.waterfall-img{
	    		width: 100%;
			}
	}
}

2.flex 布局

.waterfall{
    display: flex;
    flex-direction: column;
    flex-wrap: wrap;
    /* 需要指定高度 */
    height: 800px;
    .waterfall-item{
	     width: 25%;
	     .waterfall-img{
		     width: 100%;
		 }
	 }
 }

3.定位布局

<template>
    <div class="waterfall">
        <div 
            v-for="item in images" 
            :key="item.id" 
            :style="`width:${maxWidth}px`" 
            class="waterfall-item">
            <div class="waterfall-box" :style="`height:${item._height}px`">
                <!-- 限免标识 -->
                <div class="waterfall-free">限免</div>
                <!-- 图片 -->
                <img class="waterfall-img" :src="item.image" alt="item" />
            </div>
        </div>
    </div>
</template>  
  
<script setup>
/**
 * 父组件传递过来的数据
 * @param images 数据列表
 * @param column 列数
 * @param maxWidth 最大宽度
 * @param arrLength 当前数组长度
 */
const props = defineProps(['images','arrLength' ,'column', 'maxWidth'])

const data = reactive({
    // 开始要排列的图片索引,首次为第二列的第一张图片,后续加载则为已经排列图片的下一个索引
    beginIndex: 0,
    // 高度数组
    colsHeightArr: [],
    // 预加载参数
    loadedCount: 0,
})

const { beginIndex, colsHeightArr, loadedCount } = toRefs(data)

// 监听数据
watch(() => props.images, (news) => {
    if (news.length == 0) return
    preload()
})

// 有 tab 栏切换时需要把 beginIndex 值清空
const setBegin = () => {
    beginIndex.value = 0
}

// 预加载
const preload = () => {
    props.images.map((item, index) => {
        // 只对新加载图片进行预加载
        if (index < loadedCount.value) return;
        let oImg = new Image();
        oImg.src = item.image;
        oImg.onload = oImg.onerror = (e) => { 
            item._height = e.type == "load"? Math.round(props.maxWidth * (oImg.height / oImg.width)) : props.maxWidth
        }
        // 判断预加载是否循环完成
        if (props.arrLength == index+1) {
            setTimeout(() => {
                waterfall()
            },50)
        }
    })
}

// waterfall布局
const waterfall = () => {
    // 所有 waterfall-item 元素
    let imgBoxEls = document.getElementsByClassName('waterfall-item')
    // 如果没有元素 直接return
    if (!imgBoxEls) return
    // 每个图片最大宽度
    let top, left, height, colWidth = props.maxWidth
    // 索引是否为 0
    if (beginIndex.value == 0) {
        colsHeightArr.value = []
    }
    // 循环数据列表
    for (let i = beginIndex.value; i < props.images.length; i++){
        // 如果没有对应的元素 直接return
        if (!imgBoxEls[i]) return
        height = imgBoxEls[i].offsetHeight;
        if (i < props.column) {
            colsHeightArr.value.push(height)
            top = 0;
            left = i * colWidth;
        } else {
            // 最低高低
            let minHeight = Math.min.apply(null, colsHeightArr.value)
            // 最低高度的索引
            let minIndex = colsHeightArr.value.indexOf(minHeight); 
            // 设置元素定位的位置
            top = minHeight;
            left = minIndex * colWidth;
            // 更新colsHeightArr
            colsHeightArr.value[minIndex] = minHeight + height;
        }
        imgBoxEls[i].style.left = `${left}px`;
        imgBoxEls[i].style.top = `${top}px`;
        beginIndex.value = props.images.length
    }
}

defineExpose({
    setBegin
})
</script>  
  
<style lang="scss" scoped>  
.waterfall{
    width: 100%;
    position: relative;
    .waterfall-item{
        position: absolute;
        padding: 0 12px 12px 0;
        box-sizing: border-box;
        .waterfall-box{
            height: 100%;
            width: 100%;
            overflow: hidden;
            border-radius: 4px;
            .waterfall-free{
                position: absolute;
                top: 5px;
                left: 5px;
                width: 54px;
                height: 20px;
                border-top-left-radius: 14px;
                border-bottom-right-radius: 14px;
                color: rgb(255, 255, 255);
                background: linear-gradient(90deg, rgb(66, 66, 253) 0%, rgb(170, 103, 255) 100%, rgb(170, 103, 255) 100%);
                text-align: center;
                font-style: italic;
                z-index: 10;
            }
            .waterfall-img{
                width: 100%;
                height: 100%;
                object-fit: cover;
                cursor: pointer;
                transition: all 1s;
            }
        }
    }
    .waterfall-item .waterfall-img:hover {
        transform: scale(1.2);
    }
}
</style>
<Inspiration ref="waterfallRef" :arrLength="images.length" :maxWidth="300" :images="images" :column="4" ></Inspiration>

import Inspiration from '~/components/plaza/inspiration.vue'

const waterfallRef = ref()
// 调用方法
waterfallRef.value.setBegin()
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

卷不动了阿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值