Vue封装一个瀑布流图片容器组件

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
简介: 🎈最近在捣鼓自己的个人博客网站,有一个模块需要用到瀑布流图片🖼展示,于是我就将其封装成了一个组件,以后可以导入就能使用,具体效果如下👇

说在前面

🎈最近在捣鼓自己的个人博客网站,有一个模块需要用到瀑布流图片🖼展示,于是我就将其封装成了一个组件,以后可以导入就能使用,具体效果如下👇:

1649245798(1).png

什么是瀑布流

瀑布流,又称瀑布流式布局。是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。最早采用此布局的网站是Pinterest,逐渐在国内流行开来。国内大多数清新站基本为这类风格。

实现思路

实现瀑布流布局主要有两种思路,一种是固定图片宽度和容器宽度,动态计算展示图片的列数;另一种则是固定图片展示的列数,通过容器宽度动态计算图片的宽度,接下来让我们一起来看看这两种不同的实现方式吧。

一、固定图片宽度

1649246513(1).png
假设我们现在有这么一个容器和一些图片,应该怎么使用瀑布流布局将图片在容器中展示呢?

1、计算容器可以展示图片列数

设图片的宽度为imgWidth,容器宽度为contentWidth,那么容器可以展示图片的列数就应该为:
parseInt(contentWidth / imgWidth)或者使用Math.floor来计算也可以,就是最大列数应该是容器宽度除于图片宽度向下取整的值,如下图:

1649246825(1).png
剩下的区域不足以放下多一张图片,所以应该向下取整。

2、使用图片填充每一列

知道列数了之后那么我们就可以开始使用图片来对每一列进行填充了,如下图,容器可以划分成这么5列。

1649247109(1).png
接下来用下面图片填充进容器中
1649247273(1).png

Excalidraw 2022_4_6 20_15_26 00_00_00-00_00_30.gif
如上图动画演示一般,最后的结果如下图(数字代表图片顺序):

1649247541(1).png
看到这里是不是马上就明白了,每次图片插入的时候总是会选择当前高度最低的列进行插入如第6张应该插入到第二列的第二张下面,那么用代码要怎么实现呢?

3、代码实现

html
<template>
    <div id="img-content" class="img-content">
        <img
            class="img"
            :style="'width:' + imgWidth + 'px;'"
            v-for="(item, index) in imgList"
            :key="'img' + index"
            :src="item"
        />
    </div>
</template>
CSS
<style lang="scss" scoped>
.img-content {
    height: 30vh;
    width: 320px;
    position: relative;
    .img {
        position: absolute;
        vertical-align: top;
        margin: 3px;
    }
}
</style>
JavaScript
参数配置

我们需要接收参数有:

  • (1)imgList:展示的图片列表;
  • (2)imgWidth:图片的大小;
  • (3)imgMargin:图片的间距。
props: {
    imgList: {
        type: Array,
        default: () => []
    },
    imgWidth: {
        type: Number,
        default: 100
    },
    imgMargin: {
        type: Number,
        default: 3
    }
},
图片定位计算
  • (1)heightArr:高度数组

我们可以定义一个heightArr变量来保存当前每一列的使用高度。

  • (2)minIndex:当前高度最低的列下标

插入时需要找到当前高度最低的列,将图片插入该列。

  • (3)赋予图片top和left
// 高度数组最小的高度
const minHeight = Math.min(...heightArr);
// 高度数组最小的高度的索引
const minIndex = heightArr.indexOf(minHeight);
item.style.top = minHeight + "px";
item.style.left = minIndex * imgWidth + "px";
  • (4)完整代码
methods: {
    waterfallHandler() {
        const imgWidth = this.imgWidth + this.imgMargin * 2;
        const contentW = document.getElementById("img-content").offsetWidth;
        // 获取图片的列数
        const column = parseInt(contentW / imgWidth);
        // 高度数组
        const heightArr = new Array(column).fill(0);
        const imgList = document.getElementsByClassName("img");
        for (let i = 0; i < imgList.length; i++) {
            const item = imgList[i];
            // 当前元素的高度
            const itemHeight = item.offsetHeight;
            // 高度数组最小的高度
            const minHeight = Math.min(...heightArr);
            // 高度数组最小的高度的索引
            const minIndex = heightArr.indexOf(minHeight);
            item.style.top = minHeight + "px";
            item.style.left = minIndex * imgWidth + "px";
            heightArr[minIndex] += itemHeight;
        }
    }
}
  • (5)监听页面尺寸变化

页面尺寸发生变化时重新计算。

window.onresize = () => {
    return (() => {
        this.waterfallHandler();
    })();
};

4、完整代码

<template>
    <div id="img-content" class="img-content">
        <img
            class="img"
            :style="'width:' + imgWidth + 'px;'"
            v-for="(item, index) in imgList"
            :key="'img' + index"
            :src="item"
        />
    </div>
</template>

<script>
export default {
    name: "JWaterfall",
    props: {
        imgList: {
            type: Array,
            default: () => []
        },
        imgWidth: {
            type: Number,
            default: 100
        },
        imgMargin: {
            type: Number,
            default: 3
        }
    },
    mounted() {
        window.onresize = () => {
            return (() => {
                this.waterfallHandler();
            })();
        };
        this.waterfallHandler();
    },
    methods: {
        waterfallHandler() {
            const imgWidth = this.imgWidth + this.imgMargin * 2;
            const contentW = document.getElementById("img-content").offsetWidth;
            // 获取图片的列数
            const column = parseInt(contentW / imgWidth);
            // 高度数组
            const heightArr = new Array(column).fill(0);
            const imgList = document.getElementsByClassName("img");
            for (let i = 0; i < imgList.length; i++) {
                const item = imgList[i];
                // 当前元素的高度
                const itemHeight = item.offsetHeight;
                // 高度数组最小的高度
                const minHeight = Math.min(...heightArr);
                // 高度数组最小的高度的索引
                const minIndex = heightArr.indexOf(minHeight);
                item.style.top = minHeight + "px";
                item.style.left = minIndex * imgWidth + "px";
                heightArr[minIndex] += itemHeight;
            }
        }
    }
};
</script>

<style lang="scss" scoped>
.img-content {
    height: 30vh;
    width: 320px;
    position: relative;
    .img {
        position: absolute;
        vertical-align: top;
        margin: 3px;
    }
}
</style>

二、固定图片列数

1、计算每一列宽度

设展示的列数为column,容器宽度为contentWidth,那么容器每一列的宽度就应该为:
parseInt(contentWidth / column)或者使用Math.floor来计算也可以,也即每一张图片的宽度应该为parseInt(contentWidth / column),如下图:
image.png

2、使用图片填充每一列

1649252106(1).png
如上图,每一个li代表一列,只要对其设置浮动float:left;即可,所以这种方法不需要计算图片的具体坐标值,只需要将图片插入到高度最低的列中,具体实现步骤与上一种方法是差不多的,也是需要不断从高度最低的列进行插入,就不再赘述一遍了。

3、代码实现

html
<template>
    <div id="j-water-fall-content"></div>
</template>
CSS
<style lang="scss" scoped>
#j-water-fall-content {
    margin: 0;
    padding: 0;
}
</style>
JavaScript
参数配置

我们需要接收参数有:

  • (1)imgList:展示的图片列表;
  • (2)column:展示的列数;
  • (3)imgMargin:图片的间距。
props: {
    imgList: {
        type: Array,
        default: () => []
    },
    column: {
        type: Number,
        default: 4
    },
    imgMargin: {
        type: Number,
        default: 0.5
    }
},
动态插入图片
createImg() {
    const ul = document.getElementById("j-water-fall-content");
    let trueWidth = Math.floor(
        (100 - this.column * this.imgMargin * 2) / this.column
    );
    for (let i = 0; i < this.column; i++) {
        let li = document.createElement("li");
        li.style.listStyle = "none";
        li.style.float = "left";
        li.style.width = `${trueWidth}%`;
        li.style.margin = `0 ${this.imgMargin}%`;
        ul.appendChild(li);
        this.arr.push(li);
        this.minHeight.push(0);
    }
    let img = new Image();
    img.num = 0;
    img.src = this.imgList[img.num];
    img.style.width = "100%";
    // 当图片加载完后
    img.onload = this.loadHandler;
},
loadHandler(that) {
    const img = that.path[0];
    const minHeight = this.minHeight;
    const arr = this.arr;
    // 高度数组的最小值
    const min = Math.min.apply(null, minHeight);
    // 高度数组的最小值索引
    const minIndex = minHeight.indexOf(min);
    // 克隆一份图片
    const im = img.cloneNode(true);
    // 将图片假如对应最小值索引的容器中
    arr[minIndex].appendChild(im);
    // 更新最小值索引的容器的高度
    minHeight[minIndex] += im.height;
    img.num++;
    img.src = this.imgList[img.num];
}

4、完整代码

<template>
    <div id="j-water-fall-content"></div>
</template>

<script>
export default {
    name: "JWaterfall",
    props: {
        imgList: {
            type: Array,
            default: () => []
        },
        column: {
            type: Number,
            default: 4
        },
        imgMargin: {
            type: Number,
            default: 0.5
        }
    },
    data() {
        return {
            minHeight: [],
            arr: []
        };
    },
    mounted() {
        this.createImg();
    },
    methods: {
        createImg() {
            const ul = document.getElementById("j-water-fall-content");
            let trueWidth = Math.floor(
                (100 - this.column * this.imgMargin * 2) / this.column
            );
            for (let i = 0; i < this.column; i++) {
                let li = document.createElement("li");
                li.style.listStyle = "none";
                li.style.float = "left";
                li.style.width = `${trueWidth}%`;
                li.style.margin = `0 ${this.imgMargin}%`;
                ul.appendChild(li);
                this.arr.push(li);
                this.minHeight.push(0);
            }
            let img = new Image();
            img.num = 0;
            img.src = this.imgList[img.num];
            img.style.width = "100%";
            // 当图片加载完后
            img.onload = this.loadHandler;
        },
        loadHandler(that) {
            const img = that.path[0];
            const minHeight = this.minHeight;
            const arr = this.arr;
            // 高度数组的最小值
            const min = Math.min.apply(null, minHeight);
            // 高度数组的最小值索引
            const minIndex = minHeight.indexOf(min);
            // 克隆一份图片
            const im = img.cloneNode(true);
            // 将图片假如对应最小值索引的容器中
            arr[minIndex].appendChild(im);
            // 更新最小值索引的容器的高度
            minHeight[minIndex] += im.height;
            img.num++;
            img.src = this.imgList[img.num];
        }
    }
};
</script>

<style lang="scss" scoped>
#j-water-fall-content {
    margin: 0;
    padding: 0;
}
</style>

三、对比

在我看来方法二的实现要优于方法一,因为方法一需要一个合适的容器宽度和图片宽度,否则会出现一定空间的留白,这样的展示效果并不美观,如下图:

1649253587(1).png
因为容器宽度与图片的宽度取余过大,所以右边会留白,这样给人的视觉效果就不好了,使用方法二来实现就能很好地填充满容器的空间,如下图:

1649253730(1).png

封装成组件

为了便于自己以后复用,所以我将其放进了自己的一个组件库中,用到的时候可以直接引用。

1649255158.png

使用

如下图,在博客中使用
image.png
博客地址:http://jyeontu.xyz/JYeontuBlog/#/home

组件库地址

  • 文档

jvuewheel: http://jyeontu.xyz/jvuewheel/#/JWaterfallView

  • 源码

Gitee: https://gitee.com/zheng_yongtao/jyeontu-component-warehouse

说在后面

🎉这里是JYeontu,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球🏸 ,平时也喜欢写些东西,既为自己记录📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解🙇,写错的地方望指出,定会认真改进😊,在此谢谢大家的支持,我们下文再见🙌。
目录
相关文章
|
1天前
|
JavaScript API UED
Vue3中的Suspense组件有什么用?
Vue3中的Suspense组件有什么用?
15 6
|
1天前
|
JavaScript 前端开发
vue3中使用动态组件
vue3中使用动态组件
10 0
|
1天前
|
JavaScript 前端开发 容器
Vue 3 中 <transition-group> 组件报错的非 props 属性传递问题
Vue 3 中 <transition-group> 组件报错的非 props 属性传递问题
14 1
|
1天前
|
移动开发 JavaScript 前端开发
ruoyi-nbcio-plus基于vue3的flowable的自定义业务显示历史信息组件的升级修改
ruoyi-nbcio-plus基于vue3的flowable的自定义业务显示历史信息组件的升级修改
|
1天前
|
移动开发 前端开发
ruoyi-nbcio-plus基于vue3的flowable的自定义业务撤回申请组件的升级修改
ruoyi-nbcio-plus基于vue3的flowable的自定义业务撤回申请组件的升级修改
12 2
|
1天前
|
移动开发 前端开发
ruoyi-nbcio-plus基于vue3的flowable流程查看器组件的升级修改
ruoyi-nbcio-plus基于vue3的flowable流程查看器组件的升级修改
11 1
|
1天前
|
移动开发 前端开发
vue3 Element-Plus封装的el-tree-select的使用
vue3 Element-Plus封装的el-tree-select的使用
vue3 Element-Plus封装的el-tree-select的使用
|
1天前
|
移动开发 前端开发 JavaScript
VUE3内置组件Transition的学习使用
VUE3内置组件Transition的学习使用
|
1天前
|
监控 Kubernetes Docker
【Docker 专栏】Docker 容器内应用的健康检查与自动恢复
【5月更文挑战第9天】本文探讨了Docker容器中应用的健康检查与自动恢复,强调其对应用稳定性和系统性能的重要性。健康检查包括进程、端口和应用特定检查,而自动恢复则涉及重启容器和重新部署。Docker原生及第三方工具(如Kubernetes)提供了相关功能。配置检查需考虑检查频率、应用特性和监控告警。案例分析展示了实际操作,未来发展趋势将趋向更智能和高效的检查恢复机制。
【Docker 专栏】Docker 容器内应用的健康检查与自动恢复
|
1天前
|
Prometheus 监控 Cloud Native
构建高效稳定的Docker容器监控体系
【5月更文挑战第14天】 在现代微服务架构中,Docker容器作为应用部署的基本单元,其运行状态的监控对于保障系统稳定性和性能至关重要。本文将探讨如何构建一个高效且稳定的Docker容器监控体系,涵盖监控工具的选择、关键指标的采集、数据可视化以及告警机制的设计。通过对Prometheus和Grafana的整合使用,实现对容器资源利用率、网络IO以及应用健康状态的全方位监控,确保系统的高可用性和故障快速响应。