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

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

说在前面

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

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,现在是一名前端工程师,有空会刷刷算法题,平时喜欢打羽毛球🏸 ,平时也喜欢写些东西,既为自己记录📋,也希望可以对大家有那么一丢丢的帮助,写的不好望多多谅解🙇,写错的地方望指出,定会认真改进😊,在此谢谢大家的支持,我们下文再见🙌。
目录
相关文章
|
4天前
|
存储 JavaScript 开发者
Vue 组件间通信的最佳实践
本文总结了 Vue.js 中组件间通信的多种方法,包括 props、事件、Vuex 状态管理等,帮助开发者选择最适合项目需求的通信方式,提高开发效率和代码可维护性。
|
4天前
|
存储 JavaScript
Vue 组件间如何通信
Vue组件间通信是指在Vue应用中,不同组件之间传递数据和事件的方法。常用的方式有:props、自定义事件、$emit、$attrs、$refs、provide/inject、Vuex等。掌握这些方法可以实现父子组件、兄弟组件及跨级组件间的高效通信。
|
16天前
|
缓存 JavaScript UED
Vue 中实现组件的懒加载
【10月更文挑战第23天】组件的懒加载是 Vue 应用中提高性能的重要手段之一。通过合理运用动态导入、路由配置等方式,可以实现组件的按需加载,减少资源浪费,提高应用的响应速度和用户体验。在实际应用中,需要根据具体情况选择合适的懒加载方式,并结合性能优化的其他措施,以打造更高效、更优质的 Vue 应用。
|
20天前
|
前端开发 UED
vue3知识点:Suspense组件
vue3知识点:Suspense组件
29 4
|
19天前
|
JavaScript 前端开发 测试技术
组件化开发:创建可重用的Vue组件
【10月更文挑战第21天】组件化开发:创建可重用的Vue组件
23 1
|
20天前
|
JavaScript 前端开发 Java
《vue3第五章》新的组件,包含:Fragment、Teleport、Suspense
《vue3第五章》新的组件,包含:Fragment、Teleport、Suspense
26 2
|
20天前
|
Java
vue3知识点:Teleport组件
vue3知识点:Teleport组件
25 1
|
3天前
|
Kubernetes Cloud Native Docker
云原生时代的容器化实践:Docker和Kubernetes入门
【10月更文挑战第37天】在数字化转型的浪潮中,云原生技术成为企业提升敏捷性和效率的关键。本篇文章将引导读者了解如何利用Docker进行容器化打包及部署,以及Kubernetes集群管理的基础操作,帮助初学者快速入门云原生的世界。通过实际案例分析,我们将深入探讨这些技术在现代IT架构中的应用与影响。
12 2
|
13天前
|
Kubernetes 监控 开发者
掌握容器化:Docker与Kubernetes的最佳实践
【10月更文挑战第26天】本文深入探讨了Docker和Kubernetes的最佳实践,涵盖Dockerfile优化、数据卷管理、网络配置、Pod设计、服务发现与负载均衡、声明式更新等内容。同时介绍了容器化现有应用、自动化部署、监控与日志等开发技巧,以及Docker Compose和Helm等实用工具。旨在帮助开发者提高开发效率和系统稳定性,构建现代、高效、可扩展的应用。
|
9天前
|
关系型数据库 MySQL API