最近有相关需求,有长宽不一的图片要整齐的布局,左右上下都要对齐,实际效果打开百度图片随便搜一个关键字就能看到。 其实有一篇纯css实现的文章,我看了没懂,就自己手撸了一个js版本
这种布局的特点:
- 行和行之间高度不同,但是每行的左右上下都是对齐的,不会出现瀑布流的那种参差的情况
- 每张图片都是等比缩放,没有裁剪
先说思路再说实现:
- 把所有图片的大小拿到,把所有图片变成一样高,宽度等比缩放
- 获取当前放置图片的容器元素的宽度containerW
- 设定每一行放多少张图片,计算每一行的图片的总宽度picTotalW
- 每一行的图片用containerW除以这一行的picTotalW得出一个比例ratio,用这个ration去乘以这一行每张图片的宽度和高度,因为第一步将高度统一了,所以这一行最终的图片高度还是一样高
- 调整ratio,防止边距和小数等导致结果超过容器宽度
- 处理最后一行放不满的情况
下面来看代码实现,这里用的react
import React from "react";
import _chunk from "lodash/chunk";
import "./App.css";
// 这些图片也是从那篇文章里爬来的
const arr = [
"https://qnm.hunliji.com/Fge4p7lQoVi13jcyOZSanhn-kE9w",
"https://qnm.hunliji.com/Fv5cPUYiukLoemG11oYDEsKfnf7z",
"https://qnm.hunliji.com/Fu4dPgAhWpgxTDMDaaquoPQ76R1i",
"https://qnm.hunliji.com/FnCN5RATnuSC86_Ikq1OuLP_mVA3",
"https://qnm.hunliji.com/FtJ1aTaECF69B3agV4k_o7-Trscf",
"https://qnm.hunliji.com/Fo9VPhRXG_o8B42Z52j9ays8F9Vp",
"https://qnm.hunliji.com/Fh0RdQHU2zqbnWkOA8eFICt8th5d",
"https://qnm.hunliji.com/FoUnWii2mp4qgQ19fia55o8QE9Q9",
"https://qnm.hunliji.com/FpybsYBI2efCHb-s9uf7TC2BU2Zw",
"https://qnm.hunliji.com/FiaywmqGiuMRZgnKRmfe3uYZJhtF",
"https://qnm.hunliji.com/FuIKRPuQ8jKac_1iqND0TlkooIAe",
"https://qnm.hunliji.com/FjUjrEvym1p2P57iknOHIN_-lG8I",
"https://qnm.hunliji.com/Fpdq_wd7EfWFQgbImPIJeLxq3Es_",
// "https://qnm.hunliji.com/FvF4rUrd4Rtk-oFPoUDqhMEcybn3",
// "https://qnm.hunliji.com/FhTFQW5oZFp8mJ6axT9sbwWCORjy",
];
// 设定一些固定值,方便理解思路,这些值可以改成动态的
// num表示每行放的图片的张数,hei表示第一步统一的图片高度,margin表示图片之间的marginRight,blank是第五步里用来修正ratio用的
const num = 5;
const hei = 200;
const margin = 10;
const blank = 10;
class App extends React.Component {
imgArr = [];
state = {
lineArr: [],
containerSize: {},
};
componentDidMount() {
// 获取容器的大小
this.setState({
containerSize: document.querySelector(".App").getBoundingClientRect(),
});
const _self = this;
// 因为图片大小不一定能从接口获取,所以这里用的是new Image的方式自己去拿,拿到以后存放在imgArr里
arr.map((v) => {
const nImg = new Image();
nImg.onload = function () {
// 上来就统一了图片高度
_self.imgArr.push({
src: v,
width: (nImg.width * hei) / nImg.height,
height: hei,
});
// 将图片拆分成没num个一行
_self.setState({ lineArr: _chunk(_self.imgArr, num) });
};
nImg.src = v;
});
// 处理窗口大小变化的情况
window.onresize = function () {
_self.setState({
containerSize: document.querySelector(".App").getBoundingClientRect(),
});
};
}
render() {
const {lineArr, containerSize} = this.state;
return (
<div className="App">
{lineArr.map((v, k) => {
// 计算当前这一行的图片的宽度的和
const picTotalW = v.reduce((prev, next) => prev + next.width, 0);
// 计算ratio,这里减去了margin和一个预留的blank值,因为实现过程中发现不减掉一点总是排不下
let ratio =
(containerSize.width - (num - 1) * margin - blank) / picTotalW;
// 处理最后一行,先判断这一行是不是不足num个,如果是,计算num除以当前图片张数的比例,将容器宽度除以这个比例,
// 得出相对当前这一行的图片张数比较合适的容器宽度,然后再计算ratio,有点绕,稍微想一想
if (v.length < num) {
ratio = containerSize.width / (num / v.length) / picTotalW;
} else {
// 修正ratio,修正的思路是,如果用ratio去乘以每张图片的宽度得到最终宽度,加上边距,
// 比容器宽度大了,那么就减掉一点宽度再算一次ratio,这个减掉的东西就是cnt,以及一个blank,每次修正cnt自增
// 如果一行没有放满num张图片,是不需要修正的
let cnt = 10;
while (v.reduce((prev, next) => prev + Math.ceil(next.width*ratio), 0) + (num - 1) * margin > containerSize.width - blank) {
ratio = (containerSize.width - (num - 1) * margin - blank - cnt) / picTotalW;
cnt +=10;
}
}
return (
<div key={(v, k)} className="line">
{v.map((vv) => (
<img
className="image"
key={vv.src}
alt=""
src={vv.src}
width={vv.width * ratio}
/>
))}
</div>
);
})}
</div>
);
}
}
export default App;
不出意外的话上面这个代码可以直接跑,当然如果图片的接口能够返回长宽是最舒服的,用new Image的话如果要求顺序不变自己还要处理一下
之前提到的那些固定值,要修改成动态的话,可以按照当前视图区域的宽度来调整
样式文件就可以写的很简单了,App.css的内容如下
.line .image {
margin-right: 10px;
margin-bottom: 10px;
}
.line :last-child {
margin-right: 0;
}
效果如下:
======== 20200522更新:
把这个东西封装成组件的过程中发现其实可以不用修正ratio,向下取整也没问题