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

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

说在前面

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

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,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球🏸 ,平时也喜欢写些东西,既为自己记录📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解🙇,写错的地方望指出,定会认真改进😊,在此谢谢大家的支持,我们下文再见🙌。
目录
相关文章
|
10月前
|
Web App开发 前端开发 JavaScript
前端新利器:CSS容器查询——让组件真正“自适应
前端新利器:CSS容器查询——让组件真正“自适应
535 83
|
8月前
|
JavaScript
Vue中如何实现兄弟组件之间的通信
在Vue中,兄弟组件可通过父组件中转、事件总线、Vuex/Pinia或provide/inject实现通信。小型项目推荐父组件中转或事件总线,大型项目建议使用Pinia等状态管理工具,确保数据流清晰可控,避免内存泄漏。
726 2
|
11月前
|
人工智能 JavaScript 算法
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
Vue 中 key 属性的深入解析:改变 key 导致组件销毁与重建
1158 0
|
11月前
|
JavaScript UED
用组件懒加载优化Vue应用性能
用组件懒加载优化Vue应用性能
|
11月前
|
JavaScript 前端开发 UED
Vue 表情包输入组件的实现代码:支持自定义表情库、快捷键发送和输入框联动的聊天表情解决方案
本文详细介绍了在 Vue 项目中实现一个功能完善、交互友好的表情包输入组件的方法,并提供了具体的应用实例。组件设计包含表情分类展示、响应式布局、与输入框的交互及样式定制等功能。通过核心技术实现,如将表情插入输入框光标位置和点击外部关闭选择器,确保用户体验流畅。同时探讨了性能优化策略,如懒加载和虚拟滚动,以及扩展性方案,如自定义主题和国际化支持。最终,展示了如何在聊天界面中集成该组件,为用户提供丰富的表情输入体验。
844 8
|
11月前
|
JavaScript 前端开发 UED
Vue 表情包输入组件实现代码及详细开发流程解析
这是一篇关于 Vue 表情包输入组件的使用方法与封装指南的文章。通过安装依赖、全局注册和局部使用,可以快速集成表情包功能到 Vue 项目中。文章还详细介绍了组件的封装实现、高级配置(如自定义表情列表、主题定制、动画效果和懒加载)以及完整集成示例。开发者可根据需求扩展功能,例如 GIF 搜索或自定义表情上传,提升用户体验。资源链接提供进一步学习材料。
766 1
|
存储 JavaScript 前端开发
基于 ant-design-vue 和 Vue 3 封装的功能强大的表格组件
VTable 是一个基于 ant-design-vue 和 Vue 3 的多功能表格组件,支持列自定义、排序、本地化存储、行选择等功能。它继承了 Ant-Design-Vue Table 的所有特性并加以扩展,提供开箱即用的高性能体验。示例包括基础表格、可选择表格和自定义列渲染等。
1113 6
|
11月前
|
JavaScript 前端开发 UED
Vue 项目中如何自定义实用的进度条组件
本文介绍了如何使用Vue.js创建一个灵活多样的自定义进度条组件。该组件可接受进度段数据数组作为输入,动态渲染进度段,支持动画效果和内容展示。当进度超出总长时,超出部分将以红色填充。文章详细描述了组件的设计目标、实现步骤(包括props定义、宽度计算、模板渲染、动画处理及超出部分的显示),并提供了使用示例。通过此组件,开发者可根据项目需求灵活展示进度情况,优化用户体验。资源地址:[https://pan.quark.cn/s/35324205c62b](https://pan.quark.cn/s/35324205c62b)。
548 0
|
9月前
|
Kubernetes Docker Python
Docker 与 Kubernetes 容器化部署核心技术及企业级应用实践全方案解析
本文详解Docker与Kubernetes容器化技术,涵盖概念原理、环境搭建、镜像构建、应用部署及监控扩展,助你掌握企业级容器化方案,提升应用开发与运维效率。
1268 108