支持 由左至右,由上至下的 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}`
})
)