JavaScript实现瀑布流布局详解
本文详解如何使用JavaScript实现两列瀑布流布局。针对图片异步加载导致的高度判断错误问题,分析初始Flex布局方案的缺陷,并通过封装Promise与async/await机制,确保图片加载完成后再进行DOM插入与高度比较,从而实现正确的瀑布流视觉效果。
需求
瀑布流布局的核心特征是包含若干宽度相等的列,各列中依次放置图片、视频等元素。由于元素宽度固定但高度可能不同,整体布局会呈现参差不齐的效果。当新元素加入时,系统会自动将其插入当前高度最低的那一列,从而形成类似瀑布的视觉层级。
为简化演示,本文仅实现包含两列等宽图片的布局结构。
思路
两列布局可直接采用Flex布局实现。需注意,flex属性中align-items不应设为center,否则图片会在列内垂直居中,破坏瀑布流自然堆叠的视觉效果。通过定义left和right两个子列,并设置相等的宽度,即可完成基础结构与样式的构建。
接下来编写JavaScript逻辑:获取left和right列的clientHeight,若左侧高度小于等于右侧,则将图片插入左侧,否则插入右侧。遍历所有图片元素,依据该规则依次执行插入操作。
代码实现
html部分
<!-- 瀑布流父容器 -->
<div class="container">
<!-- 两列等宽布局 -->
<div class="col left"></div>
<div class="col right"></div>
</div>css部分
.container {
width: 700px;
background-color: aliceblue;
margin: auto;
/* flex布局 */
display: flex;
align-items: flex-start;
}
.col {
flex-basis: 350px;
}
.col img {
/* 固定图片的宽度 */
width: 100%;
}JavaScript部分
// 获取三个元素
let container = document.getElementsByClassName('container')[0]
let left = document.getElementsByClassName('col')[0]
let right = document.getElementsByClassName('col')[1]
// 插入图片
function initImg() {
for (let i = 1; i < 27; i++) {
let img = new Image();
img.src = "./pictures/" + i + ".jpg"
if (left.clientHeight <= right.clientHeight) {
left.appendChild(img)
} else {
right.appendChild(img)
}
}
}
initImg()如代码所示,获取了父元素和左右两列,然后遍历每一张图片,依次判断插入即可。看上去很完美,但是真的如此吗?
实现效果

看上去貌似很完美,也有瀑布的样子。但当拉到页面底部发现:

左边的一大块都是空的,全部放在了右边。这显然不对,因为按照逻辑,左边更短,应该加在左边才对。
问题和修正
问题就在于,img的加载是个异步的过程。再看刚才的for循环:
// 插入图片
function initImg() {
for (let i = 1; i < 27; i++) {
let img = new Image();
img.src = "./pictures/" + i + ".jpg"
if (left.clientHeight <= right.clientHeight) {
left.appendChild(img)
} else {
right.appendChild(img)
}
}
}new了Image对象后,指定了其src,然后立刻判断左右两边的高度。这时,img还没有加载完毕。然而,for循环不会等待它加载完毕。下一张图片产生后,立刻也会判断,但此时上一张图片还没有放到页面上,所以左右的高度很可能就是错误的,从而产生了错误的判断。这就出现了上图中,右列出现了很多很多图片,而左列空白的样子。
解决办法就是,把这个循环写成异步的,只有上一张图片加载完毕后,才去判断下一张图片。
很容易想到,使用Promise完成异步判断。但是对于循环中的Promise,很难清楚地通过then的变化来推进。于是,我决定采用async和await。
这就需要再封装一个方法,这个方法返回Promise,在Promise中加载某一张图片。然后再遍历所有图片,使用async/await,依次调用这个方法,就能得到结果了。
而在Promise中,我们在什么时候调用resolve呢?这就需要监听img的onload事件,设置onload事件的回调函数,在回调函数中调用resolve即可。
经过分析,再次完善代码:
// 加载第index张图片
function loadIndexImg(index) {
return new Promise((resolve, reject) => {
// 当前加载的图片
let img = new Image();
img.src = './pictures/' + index + '.jpg'
img.onload = () => {
if (left.clientHeight <= right.clientHeight) {
left.appendChild(img)
} else {
right.appendChild(img)
}
resolve();
}
})
}
// 插入图片
async function initImg() {
// 要在加载完并插入图片后才去判断下一张,因此采用async/await
for (let i = 1; i <= 26; i++) {
await loadIndexImg(i)
}
}
initImg()可以看到,在initImg中,依次调用loadIndexImg,并且是异步调用。在图片加载完成后再去加载下一张图片,效果应该就可以了。
修正后效果

效果很好!!!
总结
本文实现了简单的两列瀑布流布局,其中需要用到Promise等异步操作。
到此这篇关于JavaScript实现瀑布流布局详解的文章就介绍到这了,更多相关JS瀑布流布局内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!
ES6模块化中js文件怎么用import和export交互
2026-11-05 17:00:48
JS读取本地文件时报错或无响应的常见原因
2026-11-05 16:32:31
let、const与var的区别:JavaScript变量声明最佳实践
2026-11-05 14:16:38
JS中直接定义静态二维数组的字面量写法
2026-11-05 14:16:08
JS 怎么查找字符串中所有匹配项的位置
2026-11-05 14:15:53
