瀑布流布局

最近在技术群中总能看见小伙伴问你们瀑布流用的是什么插件?想了想好像我也不太懂瀑布流,于是乎就开始了瀑布流学习之路,也就有了下面这篇文章

写的不好请多担待

什么是瀑布流布局?

瀑布流布局如上图所示,由多列组成(至少两列),且每一列中的元素都是固定宽度,高度不固定。在淘宝的物品列表、百度图片等地方都能看见类似案例。

有几种实现方式?

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
喜欢就支持一下吧
点赞0 分享