最近在技术群中总能看见小伙伴问你们瀑布流用的是什么插件?想了想好像我也不太懂瀑布流,于是乎就开始了瀑布流学习之路,也就有了下面这篇文章
写的不好请多担待
什么是瀑布流布局?
瀑布流布局如上图所示,由多列组成(至少两列),且每一列中的元素都是固定宽度,高度不固定。在淘宝的物品列表、百度图片等地方都能看见类似案例。
有几种实现方式?
1.通过css实现(不是本次分享重点)
优点:
- 使用方便,只需要几行css代码即可
缺点:
- 兼容性不行(css3)
实现方式:
// 在最外层的父容器的css中设置需要展示的列数,及间隔
columns: 3
gap: 10
2.通过JavaScript实现
缺点:
- 需要引入特定的js库(或者写大量的代码)
优点:
- 具有较好的兼容性
完整版手写JavaScript代码:
;
(function (doc) {
var Waterfall = function (el, opt) {
this.el = document.getElementsByClassName(el)[0];
this.el.style.position = 'relative';
this.column = opt.column;
this.gap = opt.gap;
this.itemWidth = (this.el.offsetWidth - this.gap * (this.column - 1)) / this.column;
this.heightArr = [];
this.onBottomFun = opt.onBottomFun;
}
/**
* 页面初始化
* @param {Array} data 第一页渲染的数据
* @param {Number} pageIndex 初始页码
*/
Waterfall.prototype.init = function (data, pageIndex) {
this.renderList(data, pageIndex)
this.bindEvent()
}
Waterfall.prototype.bindEvent = function () {
window.addEventListener('scroll', this.scrollToBottom.bind(this), false)
}
Waterfall.prototype.scrollToBottom = function () {
if (this.getScrollTop() + this.getWindowHeight() === this.getScrollHeight()) {
this.onBottomFun();
}
}
Waterfall.prototype.getScrollTop = function () {
var scrollTop = 0,
bodyScrollTop = 0,
documentScrollTop = 0;
if (document.body) {
bodyScrollTop = document.body.scrollTop;
}
if (document.documentElement) {
documentScrollTop = document.documentElement.scrollTop;
}
scrollTop = (bodyScrollTop - documentScrollTop > 0) ? bodyScrollTop : documentScrollTop;
console.log('scrollTop', scrollTop)
return scrollTop
}
Waterfall.prototype.getScrollHeight = function () {
var scrollHeight = 0,
bodyScrollHeight = 0,
documentScrollHeight = 0;
if (document.body) {
bodyScrollHeight = document.body.scrollHeight;
}
if (document.documentElement) {
documentScrollHeight = document.documentElement.scrollHeight;
}
scrollHeight = (bodyScrollHeight - documentScrollHeight > 0) ? bodyScrollHeight : documentScrollHeight;
console.log('scrollHeight', scrollHeight)
return scrollHeight;
}
Waterfall.prototype.getWindowHeight = function () {
var windowHeight = 0;
if (document.compatMode === 'CSS1Compat') {
windowHeight = document.documentElement.clientHeight;
} else {
windowHeight = document.body.clientHeight;
}
console.log('windowHeight', windowHeight)
return windowHeight;
}
// 渲染列表
Waterfall.prototype.renderList = function (data, pageIndex) {
/**
* DocumentFragments 是DOM节点。它们不是主DOM树的一部分。通常的用例是创建文档片段,将元素附加到文档片段,
* 然后将文档片段附加到DOM树。在DOM树中,文档片段被其所有的子元素所代替。
* 因为文档片段存在于内存中,并不在DOM树中,所以将子元素插入到文档片段时不会引起页面回流(对元素位置和几何上的计算)。
* 因此,使用文档片段通常会带来更好的性能。
*/
var oFrag = doc.createDocumentFragment(); // 创建文档片段
data.forEach(function (elm, index) {
var oItem = doc.createElement('div'),
oImg = new Image();
// 设置基本样式
oItem.style.position = 'absolute';
oItem.style.overflow = 'hidden';
oImg.style.width = '100%';
oImg.style.display = 'block';
// 设置每个元素的宽高
oItem.style.width = this.itemWidth + 'px';
oItem.style.height = elm.height * this.itemWidth / elm.width + 'px';
oImg.src = elm.img;
oItem.appendChild(oImg);
// 第一行元素
if (index < this.column && pageIndex === 0) {
oItem.style.top = '0px';
oItem.style.left = index * (this.itemWidth + this.gap) + 'px';
// 存储每一列元素的高度
this.heightArr.push(elm.height * this.itemWidth / elm.width + this.gap);
// 存储每一列元素的left值
} else {
// 非第一行元素
// 获取目前高度最矮的那一列下标
var minIndex = getMinIndex(this.heightArr);
// 将当前元素插入到高度最矮的那一列下方
oItem.style.top = this.heightArr[minIndex] + 'px';
oItem.style.left = minIndex * (this.itemWidth + this.gap) + 'px';
// 更新最矮一列元素的高度
this.heightArr[minIndex] = this.heightArr[minIndex] + elm.height * this.itemWidth / elm.width + this.gap
}
// 将元素插入到文档片段中
oFrag.appendChild(oItem);
}, this)
// 将文档片段插入到DOM树中
this.el.appendChild(oFrag)
}
// 获取数组中最小数值的下标
function getMinIndex(arr) {
return arr.indexOf(Math.min.apply(null, arr));
}
// 将Waterfall构造函数挂载在window上
window.Waterfall = Waterfall;
})(document);
使用方式:
new Waterfall('J_waterfall', { // J_waterfall 为容器的类名
column: 2,
gap: 10,
onBottomFun: function () { // 当页面触底时触发
// 触底后在这里进行传入数据等操作
this.renderList(arr2, pageIndex);
},
}).init(arr1, 0) // 在初始化的时候传入第一页需要展示的数据及页码0
// 传入的数据必须如下格式
// var arr1 = [{
// width: 360,
// height: 460,
// img: 'https://picsum.photos/360/460?random=1'
// }]
瀑布流的实现原理(建议结合上面的手写源码一起看)
-
首先需要根据配置的column和gap计算出每一个元素的固定宽度
-
这个时候我们就可以布局第一行元素了
我们是通过
absolute进行布局的,所以第一行元素的top都为0px,每个元素的left为index * (this.itemWidth + this.gap) + 'px'index为每个元素的下标
-
我个人的理解是瀑布流中不存在行的概念(第一行除外),在创建第一行元素的时候需要保存每一列目前的高度。接下去的元素需要插入在高度最短的那一列中,插入后需要更新此列高度,依次循环
总结
网络上已经有很多瀑布流布局的插件了,但是最好还是自己写一遍,有利于你理解它,以后碰到问题也更好解决。