你可以准备一些长宽都不相等的图片。

瀑布流特点

  1. 等宽不等高
  2. 为了让最后一行的差距最小,从第二行开始,需要将图片放在第一行最矮的图片下面
实现思路
  1. 设定每一列图片的宽度和间距
  2. 获取当前窗口的总宽度,从而根据图片宽度去判断分为几列
  3. 获取所有图片元素,定义一个空数组来保存高度
  4. 遍历容器
    4.1 第一排,top固定 为gap;left 根据列数递增 为 (itemWidth+gap)i + gap
    4.2 非第一批, top( minHeight+gap
    2) left的计算方式和4.1一样,其中i的位置是minHeight所在的列数
  5. 调用
function waterFall(){
  const minGap = 20 // 粗略设定一个你想要的最小间距
  const itemWidth = 300 // 定义每一项的宽度(保证等宽)
  const scrollBarWidth = getScrollbarWidth(); //获取滚动条宽度
  const pageWidth = window.innerWidth - scrollBarWidth; // 获取当前页面的宽度
  // 实际列数= 页面宽度/(图片宽度+最小间距)
  const column - Math.floor(pageWidth/(itemWidth+minGap))
//计算真实间距
const gap = (pageWidth - itemWidth*column) / column / 2;
const items = document.querySelectorAll("img") 
const heightArr = [] // 定义一个空数组,保存最低高度

//便利所有的外层容器
for(let i = 0; i < items.length; i++){
	const height = items[i].offsetHeight;
	if(i < column){
// 索引i小于列数,当前为第一行;直接设置元素距离上一个左边距
   	   items[i].style.cssText = `top: ${gap}px; left: ${(itemWidth + gap) * i + gap}px`;
   	   // 保存当前元素的高度
   	   heightArr.push(height)
	}else{
		// 不是第一行,进行比对
		let minHeight = heightArr[0] 
		let minIndex = 0
		for(let j = 0; j < heightArr.length; j++){
			// 通过循环遍历比对,拿到最小值和最小值的索引
			if(minHeight > heightArr[j]){
				minHeight = heightArr[j]
				minIndex = j
			}
		}
		items[i].style.cssText = `top:${minHeight+gap*2}px; left: ${(itemWidth+gap)*minIndex + gap}px`
	heightArr[minIndex] = minHeight + gap + height
	

	}
}
}
// 获取滚动条宽度
function getScrollbarWidth(){
	const oDiv = document.createElement('div')
	oDiv.style.cssTex = `width:50px; height:50px; overflow:scroll;`
	document.body.appendChild(oDiv) // 把div添加到body中
	const scrollbarWidth = oDiv.offsetWidth - oDiv.clientWidth; // 使最大宽度和可视宽度相减,获得滚动条宽度
	oDiv.remove();// 移出创建的div
	return scrollbarWidth; // 返回滚动条宽度
}
window.onload = waterFall
window.onresize = waterFall;

完整代码

<!DOCTYPE html>
<html>
<head>    
    <style>
        .box {
            width: 100%;
            height: 100%;
            position:relative;
        }       
        .item img{            
            position: absolute;
            width: 300px;
        }
    </style>
</head>
<body>
    <div class="box" style="border: 10px red solid;">
        <div class="item">
            <img  src="1.jpeg" alt="" />
        </div>
        <div class="item" style="border: 1px red solid;">
            <img  src="2.jpeg" alt="" />
        </div>
        <div class="item">
            <img  src="3.jpeg" alt="" />
        </div>
        <div class="item">
            <img  src="4.jpeg" alt="" />
        </div>
        <div class="item">
            <img  src="5.jpeg" alt="" />
        </div>
        <div class="item">
            <img  src="6.jpeg" alt="" />
        </div>
        <div class="item">
            <img  src="7.jpeg" alt="" />
        </div>
        <div class="item">
            <img  src="8.jpeg" alt="" />
        </div>
        <div class="item">
            <img  src="9.jpeg" alt="" />
        </div>
        <div class="item">
            <img  src="10.jpeg" alt="" />
        </div>
        <div class="item">
            <img  src="11.jpeg" alt="" />
        </div>
        
    </div>
</body>
1 <script type="text/javascript">
        // 定义瀑布流算法函数
        function fall() {
            const minGap = 10; // 最小间距,让每一列的最小空隙可以自定义,避免太过拥挤的情况发生。但是,会通过计算得到真实的间距。
            const itemWidth = 300; // 每一项的宽度,即当前每一个图片容器的宽度。保证每一列都是等宽不等高的。
            const scrollBarWidth = getScrollbarWidth();    // 获取滚动条的宽度
            console.log("##############3",scrollBarWidth);
            const pageWidth = window.innerWidth - scrollBarWidth; // 获取当前页面的宽度 = window.innerWidth - 滚动条的宽度
            const column = Math.floor(pageWidth / (itemWidth + minGap)); // 实际列数=页面宽度/(图片宽度+最小间距)
            const gap = (pageWidth - itemWidth * column) / column/2; // 计算真实间距 = (页面宽度- 图片宽度*实际列数)/实际列数/2
            const items = document.querySelectorAll('img'); // 获取所有的外层元素

            
            const heightArr = []; // 定义一个空数组,保存最低高度。
            
            // 获取滚动条的宽度
            function getScrollbarWidth() {
                const oDiv = document.createElement('div');//创建一个div
                // 给div设置样式。随便定义宽高,只要能获取到滚动条就可以
                oDiv.style.cssText = `width: 50px;height: 50px;overflow: scroll;` 
                document.body.appendChild(oDiv);//把div添加到body中
                const scrollbarWidth = oDiv.offsetWidth - oDiv.clientWidth;// 使最大宽度和可视宽度相减,获得到滚动条宽度。
                oDiv.remove();//移除创建的div
                return scrollbarWidth;//返回滚动条宽度
            }
            
            
            for (let i = 0; i < items.length; i++) {
                // 遍历所有的外层容器
                const height = items[i].offsetHeight;
                // 如果当前处在第一行
                if (i < column) {
                    // 直接设置元素距离上部的位置和距离左边的距离。
                    items[i].style.cssText = `top: ${gap}px;left: ${(itemWidth + gap) * i + gap}px`;                                        
                    // 保存当前元素的高度。
                    heightArr.push(height);
                } else {
                    // 不是第一行的话,就进行比对。
                    let minHeight = heightArr[0]; // 先保存第一项的高度
                    let minIndex = 0; // 保存第一项的索引值
                    for (let j = 0; j < heightArr.length; j++) {
                        // 通过循环遍历比对,拿到最小值和最小值的索引。
                        if (minHeight > heightArr[j]) {
                            minHeight = heightArr[j];
                            minIndex = j;
                        }
                    }
                    // 通过最小值为当前元素设置top值,通过索引为当前元素设置left值。
                    items[i].style.cssText = `top: ${minHeight + gap *2}px; left: ${(itemWidth + gap) * minIndex + gap}px`;
                    // 并修改当前索引的高度为当前元素的高度
                    heightArr[minIndex] = minHeight + gap + height;
                }
            }
        }
        // 页面加载完成调用一次。
        window.onload = fall;        
        // 页面尺寸发生改变再次调用。
        window.onresize = fall;
    </script>
</html>

参考
关于瀑布流的布局原理…