Flex布局,几行代码就可以实现瀑布流布局,代码简单,定制化强。

原理很简单,计算图片的宽高,再计算每列的使用高度,然后再将当前图片放置在列高最小的一列。其实这种方式使用什么方式布局都无所谓,我使用的是flexd布局。Flex的使用在这里就不讲解了,网上的教程一大堆。这里讲解使用VUE3实现,并封装成可以使用的组件。

话不多说,上效果
h5-editor

以下是核心代码。

template

<template>
    <div :id="fluidId" v-infinite-scroll="loadMore" :infinite-scroll-distance="20" :infinite-scroll-immediate="false" class="uabs uof-x uof-y-s">
        <div v-if="imgList.length > 0" class="ub ub-f1 upad-rl06">
            <div v-for="i in col" :key="i" class="ub ub-f1 ub-ver" :style="'margin-left:' + (i != 1 ? gutter : 0) + 'px'">
                <el-image v-for="img, idx in imgList[i - 1]" @click="emit('getData', img)" @dragstart="emit('dragstart', img)" @dragend="emit('dragend', img)" :key="idx" :src="img.url || img.thumbnailUrl" loading="lazy" fit="scale-down" class="img-hover uba ushadow" :style="'margin-top:' + gutter + 'px'">
                    <template #error>
                        <el-icon class="ub ub-ac ub-pc ub-fv ub-fh uc-font-gray2 uf-s2" style="height: 50px;">
                            <Picture />
                        </el-icon>
                    </template>
                </el-image>
            </div>
            <div class="uhide">
                <el-image v-for="img, idx in data" :key="idx" :src="img.url || img.thumbnailUrl" @load="load(img)" @error="error(img)" fit="scale-down"></el-image>
            </div>
        </div>
        <el-empty v-if="data.length==0 && isReturn" :image-size="100"></el-empty>
        <div v-else class="ub ub-ac ub-pc upad-t1">
            <el-button v-if="hasMore" type="info" link :loading="!loadOver" @click="loadMore">
                {{ loadOver ? 'more' : 'loading' }}
                <el-icon v-show="loadOver">
                    <ArrowDownBold />
                </el-icon>
            </el-button>
            <el-divider v-else><span class="uf-s06 uc-font-gray1">No more</span></el-divider>
        </div>
    </div>
</template>

以上代码的主要部分是隐藏图片加载,计算当前图片的宽高。

javascript

<script setup>
import {
    ref,
    onMounted
} from 'vue';
import { ArrowDownBold } from '@element-plus/icons-vue';
const emit = defineEmits(['loadMore', 'getData', 'dragstart']);

const props = defineProps({
    col: {
        type: Number,
        default: 2
    },
    gutter: {
        type: Number,
        default: 10
    },
    data: {
        type: Array,
        default: []
    },
    hasMore: {
        type: Boolean,
        default: false
    },
    isReturn: {
        type: Boolean,
        default: false
    }
});
const fluidId = ref(new Date().getTime());
let colWidth = 0;
let imgTotalHeight = [];
const imgList = ref([]);
let loadCount = 0;
const loadOver = ref(false);
const load = (img) => {
    if (colWidth == 0) {
        const dom = document.getElementById(fluidId.value);
        colWidth = (dom.clientWidth / props.col) - (props.col - 1) * props.gutter;
    }
    const temImg = new Image();
    temImg.src = img.url || img.thumbnailUrl;
    
    temImg.onload = function () {
        const width = temImg.width;
        const height = temImg.height;
        const minTotalHeight = Math.min(...imgTotalHeight);
        const colIdx = imgTotalHeight.indexOf(minTotalHeight);
        img.width = width;
        img.height = height;
        imgTotalHeight[colIdx] += (height * colWidth) / width;
        imgList.value[colIdx].push(img);
        loadCount++
        if (loadCount == props.data.length) {
            loadOver.value = true;
        }
    }
}
const error = (img) => {
    const height = 50;
    const minTotalHeight = Math.min(...imgTotalHeight);
    const colIdx = imgTotalHeight.indexOf(minTotalHeight);
    imgTotalHeight[colIdx] += height;
    imgList.value[colIdx].push(img);
    loadCount++;
    if (loadCount == props.data.length) {
        loadOver.value = true;
    }
}

const loadMore = () => {
    if (props.hasMore) {
        loadOver.value = false;
        emit('loadMore', true);
    }
}

const init = () => {
    loadCount = 0;
    loadOver.value = false;
    imgTotalHeight = [];
    imgList.value = [];
    for (let i = 0; i < props.col; i++) {
        imgTotalHeight.push(0);
        imgList.value.push([]);
    }
}
defineExpose({ init });

onMounted(() => {
    init();
})
</script>

以上代码核心代码为load,计算图片的宽高,再计算每列的高度,将图片放置在较矮的那一列。

接收的Props
col:展示的列数
gutter: 每列之前的距离,单位px
data: 图片列表,如:[{url:url1},{url:url2}]
hasMore: 是否有更多,为true向下滚动可自动加载
isReturn:展示loading用

暴露的方法
loadMore:加载更多
getData:点击当前图片
dragstart:拖拽当前图片

style

.ub {
  position: relative;
  display: -webkit-flex;
  display: flex;
  flex-direction: row;
}
.ub-ac {
  justify-content: center;
} 

.ub-pc {
  align-items: center;
}
.ub-fh {
  width: 100%;
}

.ub-fv {
  height: 100%;
}
.fluid-img {
    border-radius: 5px;
    overflow: hidden;
    min-height: 30px;
}

如何使用

<template>
  <template v-else>
    <Fluid ref="fluidCom" :col="3" :gutter="10" :data="dataList" :is-return="isReturn" :has-more="hasMore" @load-more="getMoreMaterial"></Fluid>
  </template>
<script setup>
  import Fluid from '@/components/Fluid.vue';
</script>

代码已开源,可参考源码加载资源部分。代码简单,定制化强,不防试试。
见github

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值