js实现500px、百度图片布局

1,146 阅读3分钟

最近有相关需求,有长宽不一的图片要整齐的布局,左右上下都要对齐,实际效果打开百度图片随便搜一个关键字就能看到。 其实有一篇纯css实现的文章,我看了没懂,就自己手撸了一个js版本

这种布局的特点:

  1. 行和行之间高度不同,但是每行的左右上下都是对齐的,不会出现瀑布流的那种参差的情况
  2. 每张图片都是等比缩放,没有裁剪

先说思路再说实现:

  1. 把所有图片的大小拿到,把所有图片变成一样高,宽度等比缩放
  2. 获取当前放置图片的容器元素的宽度containerW
  3. 设定每一行放多少张图片,计算每一行的图片的总宽度picTotalW
  4. 每一行的图片用containerW除以这一行的picTotalW得出一个比例ratio,用这个ration去乘以这一行每张图片的宽度和高度,因为第一步将高度统一了,所以这一行最终的图片高度还是一样高
  5. 调整ratio,防止边距和小数等导致结果超过容器宽度
  6. 处理最后一行放不满的情况

下面来看代码实现,这里用的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,向下取整也没问题