实现瀑布流布局的三种方式

2,936 阅读2分钟

文章借鉴:juejin.cn/post/701465…

方法一:column实现

  • column-count: 定义屏幕分为多少列
  • column-gap: 定义列与列之间的距离

    body{
        margin: 0;
        list-style: none;
    }
    #box{
        margin: 40px;
        column-count: 5;
        column-gap: 20px;
    }
    #box > li > img{
        width: 100%;
        height: 100%;
    }

1.png

优点:

  • 实现简单
  • 图片自动填充不用考虑图片加载状态

缺点:

  • 兼容性差
  • 排列规律永远都是先上下在左右,无法控制

方法二:flex布局实现

body{
    margin: 0;
    list-style: none;
}
#box{
    display: flex;
    flex-flow: column wrap;
    height: 2000px;
}
#box > li{
    margin: 10px;
    width: calc(100% / 4 - 20px);
}
#box > li > img{
    width: 100%;
    height: 100%;
}

2.png

优点:

  • 实现相对简单
  • 图片自动填充不用考虑图片加载状态
  • 顺序在一定程度上可以改变

缺点:

  • 高度是固定的

在小型项目中可以使用上述的方法,但是实际开发还是以JS方式实现

方法三:JS实现

export default class Waterfall {
    /*
    * $el:父容器
    * width:每张图片宽度
    * items:所有子元素
    * H:存储每一列的高度
    * flag:虚拟DOM节点集合
    * */
    constructor(options) {
        this.$el = null
        this.count = 4
        this.gap = 10
        Object.assign(this, options)
        this.width = 0
        this.items = []
        this.H = []
        this.flag = null
        this.init()
        console.log('ok')
    }

    init() {
        this.items = Array.from(this.$el.children)
        this.reset()
        this.render()
    }

    reset() {
        this.flag = document.createDocumentFragment()  //创建一个空白文档
        this.width = this.$el.clientWidth / this.count
        this.H = new Array(this.count).fill(0)
        this.$el.innerHTML = ""
    }

    render() {
        const {width, items, flag, H, gap} = this
        items.forEach(item => {
            item.style.width = width + 'px'
            item.style.position = 'absolute'
            let img = item.querySelector('img')
            /* 图片是否加载完成 */
            if (img.complete) {
                /* 获取每一列的最小高度 */
                let tag = H.indexOf(Math.min(...H));
                item.style.left = tag * (width + gap) + 'px'
                item.style.top = H[tag] + 'px'
                img.style.width = '100%'
                img.style.height = '100%'
                H[tag] += img.height * width / img.width + gap
                flag.appendChild(item)
            } else {
                img.addEventListener('load', () => {
                    let tag = H.indexOf(Math.min(...H))
                    item.style.left = tag * (width + gap) + 'px'
                    item.style.top = H[tag] + 'px'
                    img.style.width = '100%'
                    img.style.height = '100%'
                    H[tag] += img.height * width / img.width + gap
                    flag.appendChild(item)
                    this.$el.append(flag)
                })
            }
        })
        this.$el.append(flag)
    }
};

3.png

实现原理:

  • 初始化,计算出列宽来,将H作为列高存储器。然后计算子元素,清除父容器内容。
  • 遍历子元素,设置其都为绝对定位,设置其列宽,然后监听其下的图片是否加载完毕。
  • 如果加载完毕,那么计算应该所在的位置,瀑布流的核心就是哪一列高度最小就在那一列上设置新的图片。当然他的相对高度和间距也要计算出来,同时在当前的H上把高度存起来。
  • 每次图片加载完就更新虚拟节点到父容器中。

优点:

  1. 控制灵活,随意扩展
  2. 也可以无限加载,不用过多考虑兼容性问题
  3. 同时可以添加诸多动画来增强用户体验

缺点:

  1. 实现相对复杂
  2. 图片填充需要考虑图片加载状态
  3. 性能相对纯css实现较差