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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,458评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,454评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,171评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,062评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,440评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,661评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,906评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,609评论 0 200
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,379评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,600评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,085评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,409评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,072评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,088评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,860评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,704评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,608评论 2 270

推荐阅读更多精彩内容