Skip to content

瀑布流

CSS 实现

使用 column 属性

column 可以指定容器下元素列的宽度和数量

css
columns: column-width column-count;

HTML 结构:

html
<div class="masonry">
    <div class="item">
      <img src="https://picsum.photos/360/480?random=1" alt="">
    </div>
    // ...
    <div class="item">
      <img src="https://picsum.photos/360/420?random=2" alt="">
    </div>
</div>

使用 2 个 css 属性,column-count (指定列数)和column-gap (列之间的间距)

css
body {
    margin: 4px;
    font-family: Helvetica;
}
.masonry {
    column-count: 4;
    column-gap: 0;
}
.masonry .item {
    position: relative;
    break-inside: avoid;
}
.masonry .item::after {
    counter-increment: count;
    content: counter(count);
    width: 2em;
    height: 2em;
    background-color: rgba(0,0,0,0.9);
    color: #ffffff;
    line-height: 2em;
    text-align: center;
    position: absolute;
    font-size: 1em;
    z-index: 2;
    left: 0;
    top: 0;
}

图片的排列是从上往下排列的而不是从左往右。

Sum

  1. 排列规律都是先上下再左右,无法控制,动态加载会出现问题。
  2. 兼容性不太好。

使用 FlexBox 实现

css
// 内容由上至下排列,容器的高度少于内容的高度时就换行,也就是向右排列
.masonry {
    display: flex;
    flex-direction: column;
    flex-wrap: wrap;
    height: 1000px;
}

.item {
    position: relative;
    width: 25%;
    padding: 2px;
    box-sizing: border-box;
}

.item img {
    display: block;
    width: 100%;
    height: auto;
}

flexbox 也是从上至下排列的,但是 flexbox 里的元素可以套用一个叫 order 的设定值,让它可以不跟随 HTML 结构顺序来排列

css
.item:nth-child(4n+1){
    order: 1;
}

.item:nth-child(4n+2){
    order: 2;
}

.item:nth-child(4n+3){
    order: 3;
}
// ...

Sum

  1. 高度是固定的,很难做活。
  2. 顺序虽然可以改变,但是仍然不灵活,不尽人意。

JS 实现

使用 absolute 实现:

  • 初始化,计算出列宽来,将H作为列高存储器,4列那么就是[0,0,0,0]。然后收集子元素后,清除父容器内容。

  • 遍历其子元素,设置其都为绝对定位,设置其列宽。后监听其下的图片加载是否完毕。

  • 如果加载成功,那么计算应该在的位置,瀑布流的常规原则是哪一列数值最小就在那一列上设置新图片。当然他的相对高度和间距也要计算出来,同时在H当前列上要把高度存起来。

  • 每次图片加载完就更新虚拟节点到父容器中。

javascript
import Waterfall from "./Waterfall.js"
 window.onload = new Waterfall({
    $el: document.querySelector(".masonry"),
    count: 4,
    gap: 10
})

Waterfall.js 类

javascript
export default class Waterfall {
    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();
    }
    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";                  
                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";                  
                    H[tag] += img.height*width/ img.width + gap;
                    flag.appendChild(item);
                    this.$el.append(flag);
                })
            }
        })
        this.$el.append(flag);
    }
}

大部分商用瀑布流方案如下 isotopeMasonry.js .


瀑布流的三种实现方案及优缺点

css 实现瀑布流效果