最近在技术群中总能看见小伙伴问你们瀑布流用的是什么插件?想了想好像我也不太懂瀑布流,于是乎就开始了瀑布流学习之路,也就有了下面这篇文章
写的不好请多担待
什么是瀑布流布局?
瀑布流布局如上图所示,由多列组成(至少两列),且每一列中的元素都是固定宽度,高度不固定。在淘宝的物品列表、百度图片等地方都能看见类似案例。
有几种实现方式?
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为每个元素的下标
-
我个人的理解是瀑布流中不存在行的概念(第一行除外),在创建第一行元素的时候需要保存每一列目前的高度。接下去的元素需要插入在高度最短的那一列中,插入后需要更新此列高度,依次循环
总结
网络上已经有很多瀑布流布局的插件了,但是最好还是自己写一遍,有利于你理解它,以后碰到问题也更好解决。
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END