原生JavaScript实现瀑布流布局:原理与代码详解
利用原生 JavaScript 实现瀑布流布局,主要依赖对页面可视区域的实时计算以及 DOM 元素的绝对定位。本文将解析如何通过 JS 获取窗口宽度、分配初始行数,并追踪每列高度以实现后续元素的智能排列。
瀑布流布局的核心逻辑
瀑布流(Waterfall Layout)是一种非对称的网格布局形式,其核心优势在于能够最大化利用屏幕垂直空间。在纯原生 JavaScript 环境下,实现该布局主要分为三个关键环节:
- 确定列数:根据父容器宽度和单个元素的固定宽度,计算出当前视口下每一行能容纳多少个元素。
- 首排占位:将第一行的元素依次放置在父容器中,作为基准列,并记录每列当前的累积高度。
- 后续定位:对于第二行及之后的元素,寻找当前高度最小的那一列,将新元素放置在该列的最下方(即 top 值为最小高度),并更新该列的高度。
这一过程需要配合窗口的 onscroll 事件,当用户滚动到底部时触发数据的追加和重新布局。
环境准备
为实现该效果,我们需要一个 HTML 结构和基础的 CSS 样式来辅助定位。
- HTML 结构:创建一个 ID 为
main的父容器,内部包含多个类名为pin的子元素,每个pin代表瀑布流中的一个格子。 - CSS 样式:
#main需设置相对定位以方便内部子元素绝对定位;.pin需设置浮动属性以便在第一排时能横向排列,而后续插入的元素由 JS 统一接管绝对定位。
JS 实现步骤
第1步:封装位置计算函数
这是瀑布流的核心算法。首先获取所有用于放置内容的元素数组,计算单列宽度,进而算出总列数。随后遍历这些元素,前 N 个(N 为列数)直接放入第一排并记录高度;后续元素则对比数组中的最低高度,找到对应索引后设置为绝对定位。
function waterfall(parentId, pinClassName) {
var oParent = document.getElementById(parentId);
// 获取所有的 pin 元素
var aPins = getClassObj(oParent, pinClassName);
// 获取单个 pin 的宽度
var iPinW = aPins[0].offsetWidth;
// 计算一行能容纳多少个 pin
var num = Math.floor(document.documentElement.clientWidth / iPinW);
// 限制列数不超过4列(避免过窄)
if (num > 4) {
num = 4; }
// 设置父容器居中
oParent.style.cssText = 'width:' + iPinW * num + 'px; margin: 0 auto;';
// 初始化每列高度数组
var pinHArr = [];
for (var i = 0; i < aPins.length; i++) {
var pinH = aPins[i].offsetHeight;
// 如果是第一排的元素
if (i < num) {
pinHArr[i] = pinH;
} else {
// 找出高度最小的列索引
var minH = Math.min.apply(null, pinHArr);
var minHIndex = getMinHIndex(pinHArr, minH);
// 绝对定位到最小高度列的下方
aPins[i].style.position = 'absolute';
aPins[i].style.top = minH + 'px';
aPins[i].style.left = aPins[minHIndex].offsetLeft + 'px';
// 更新该列的累计高度
pinHArr[minHIndex] += aPins[i].offsetHeight;
}
}
}
第2步:实现滚动加载更多数据
为了实现用户体验良好的“无限加载”,我们需要判断用户是否即将滚动到页面底部。当条件满足时,模拟生成新的数据 DOM 节点,并再次调用 waterfall 函数进行重绘布局。
function checkscrollside() {
var oParent = document.getElementById('main');
var aPins = getClassObj(oParent, 'pin');
// 如果高度不够,不加载
if (aPins.length == 0) { return false; }
// 获取最后一个元素距离顶部的距离加上一半自身高度
var lastPinH = aPins[aPins.length - 1].offsetTop + Math.floor(aPins[aPins.length - 1].offsetHeight / 2);
// 获取页面滚动的距离
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
// 获取可视区域高度
var documentH = document.documentElement.clientHeight;
// 触发加载的条件:最后一个元素的一半已入视野
return (lastPinH < scrollTop + documentH) ? true : false;
}
window.onscroll = function() {
if (checkscrollside()) {
// 此处应触发 AJAX 请求获取真实后端数据
addNewElement();
waterfall('main', 'pin'); // 重新计算布局
}
};
第3步:工具函数与 DOM 操作
上述代码中用到了一些辅助函数,如根据 class 获取元素的数组 getClassObj,以及查找数组最小值索引的 getMinHIndex。这些是处理 DOM 列表时的标准做法:
function getClassObj(parent, className) {
var obj = parent.getElementsByTagName('*');
var pinS = [];
for (var i = 0; i < obj.length; i++) {
if (obj[i].className == className) {
pinS.push(obj[i]);
}
};
return pinS;
}
function getMinHIndex(arr, minH) {
for (var i in arr) {
if (arr[i] == minH) {
return i;
}
}
}
视觉效果示例
经过上述代码处理后的页面,会呈现出参差不齐但视觉上整齐的排列效果。
注意事项与兼容性
在实际生产环境中,使用原生 JS 实现瀑布流需要注意以下几点:
- 图片加载延迟:由于瀑布流的定位依赖于元素的实际高度,而在图片未加载完成前,DOM 高度通常为 0 或默认高度。因此,应在图片加载完毕后再执行
waterfall函数,或者在预加载阶段预留高度占位。 - 性能优化:频繁的重排(Reflow)会导致页面卡顿。建议在
onscroll事件中增加防抖(Debounce)机制,例如在50ms内只触发一次布局计算。 - 多端适配:当浏览器窗口大小改变时,列数可能发生变化。需监听
window.onresize事件,重新调用布局函数以适应新的屏幕尺寸。
以上就是原生JavaScript实现瀑布流布局的详细内容,更多关于JS瀑布流实现的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









