支持 由左至右,由上至下的 horizontalOrder 方式 和 默认的按直行高度最小的方式布局。
使用 Promise 异步下载图片,async 与 iterator 使依次布局。
<div class="masonry"></div>
.masonry {
  position: relative;
  width: 1200px;
  margin: 0 auto;
}
.item {
  position: absolute;
  counter-increment: item-counter;
}
.item img {
  display: block;
  width: 100%;
  height: auto;
}
.item::after {
  position: absolute;
  display: block;
  top: 2px;
  left: 2px;
  width: 24px;
  height: 24px;
  text-align: center;
  line-height: 24px;
  background-color: rgba(0, 0, 0, 0.5);
  color: #fff;
  content: counter(item-counter);
}
class Waterfall {
  constructor(opt, initData) {
    this.data = initData || [];
    this.el = document.querySelector(opt.el);
    this.column = opt.column;
    this.gap = opt.gap;
    this.horizontalOrder = opt.horizontalOrder || false;
    this.itemWidth = (this.el.offsetWidth - (this.column - 1) * this.gap) / this.column;
    this.heightArr = [];
    this.init();
    window.Waterfall = Waterfall;
  }
  init() {
    this.render();
  }
  async render() {
    const iter = this.data[Symbol.iterator]();
    let src = iter.next().value;
    let oDiv = null, index = 0;
    while (src) {
      oDiv = await this.createImgTag(src);
      if (this.horizontalOrder) {
        this.renderHorizontalOrder(oDiv, index);
      } else {
        this.renderLayout(oDiv, index);
      }
      src = iter.next().value;
      index++;
    }
  }
  renderLayout(item, index) {
    item.style.width = this.itemWidth + 'px';
    if (index < this.column) {
      item.style.top = '0px';
      item.style.left = index * (this.itemWidth + this.gap) + "px";
      this.el.appendChild(item);
      this.heightArr.push(item.offsetHeight);
    } else {
      const oItems = this.el.querySelectorAll('div');
      const minIdx = getMinIdx(this.heightArr);
      item.style.left = oItems[minIdx].offsetLeft + 'px';
      item.style.top = (this.heightArr[minIdx] + this.gap) + 'px';
      this.el.appendChild(item);
      this.heightArr[minIdx] += item.offsetHeight + this.gap;
    }
    function getMinIdx(arr) {
      return arr.indexOf(Math.min(...arr));
    }
  }
  renderHorizontalOrder(item, index) {
    item.style.width = this.itemWidth + 'px';
    if (index < this.column) {
      item.style.top = '0px';
      item.style.left = index * (this.itemWidth + this.gap) + "px";
      this.el.appendChild(item);
      this.heightArr.push(item.offsetHeight);
    } else {
      const idx = index % this.column;
      item.style.left = idx * (this.itemWidth + this.gap) + 'px';
      item.style.top = (this.heightArr[idx] + this.gap) + 'px';
      this.el.appendChild(item);
      this.heightArr[idx] += item.offsetHeight + this.gap;
    }
  }
  createImgTag(imgSrc) {
    const promise = new Promise((resolve, reject) => {
      const oDiv = document.createElement('div');
      oDiv.className = 'item';
      const oImg = document.createElement('img');
      oImg.onload = () => {
        oDiv.appendChild(oImg);
        resolve(oDiv);
      };
      oImg.onerror = () => {
        const error = new Error('图片加载失败 ${imgSrc}');
        reject(error)
      }
      oImg.src = imgSrc;
    })
    return promise;
  }
}
const myRandom = (min, max) => {
  return Math.floor(Math.random() * (max - min + 1) + min);
}
const wf = new Waterfall(
  {
    el: '.masonry',
    column: 5, // column-count
    gap: 3, // column-gap
  },
  new Array(50).fill(undefined).map((_, index) => {
    return `https://picsum.photos/${myRandom(200, 700)}/${myRandom(200, 700)}?random=${index + 1}`
  })
)
 
                         
                                

