2022-06-01

相信大家对瀑布流并不陌生,瀑布流又称瀑布流式布局。是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。最早采用此布局的网站是Pinterest,逐渐在国内流行开来。国内大多数清新站基本为这类风格。

他具有以下特点:

  1. 以图片为主的展示页面
  2. 图片尺寸不一交错排布
  3. 空间使用更加充分,节省了空间

既然瀑布流适用场景那么多,我们本期就是以瀑布流在前端的三种实现方案,以及他们使用的优缺来探索。

正文

A. column方案

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

columns: column-width column-count;
复制代码

但我们瀑布流用到的css属性是:

column-count:指定列数

column-gap:列之间的差距

实现

<ul class="img-wrapper">
    <li><img src="./image/1.jpg" alt=""></li>
    <!-- 
        ... 
        ... 
        ... 
    -->
    <li><img src="./image/20.jpg" alt=""></li>
</ul>
复制代码

我们在html中先放入二十张图片作为本次演示。

.img-wrapper{
    column-count: 4;
    column-gap: 10px;

    counter-reset: count;
    width: 960px;
    margin: 0 auto;
}

.img-wrapper>li{
    position: relative;
    margin-bottom: 10px;
}
复制代码

这里我们用column-count设定为了4列,column-gap间距为10像素。就这么简单的两句我们就实现了一个瀑布流。

对了,为了我们更直观的观察排列规律,我们用伪类再做个计数器(后面的方案都会出现该伪类和html结构)。

.img-wrapper>li>img{
    width: 100%;
    height: auto;
    vertical-align: middle;
}

.img-wrapper>li::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;
}
复制代码

效果

  • [图片上传失败...(image-47233d-1654092336814)]

我们可以观察到计数下标就会知道column的排布规律是先从上往下排,然后从左到右排。

优点

  1. 实现简单,仅需两行核心代码。
  2. 图片自动填充不用考虑图片加载状态。

缺点

  1. 兼容性仍有些不尽人意。* [图片上传失败...(image-d19f63-1654092336814)]

  2. 排列规律永远都是先上下再左右,无法控制,动态加载会出现严重问题。

B. flex方案

用到的html结构与伪类计数与A方案相同,这里不再过多占用篇幅。

这个方案用到了弹性盒子,你没听错,用弹性布局也可以实现一个瀑布流,虽然很多局限性,但是也可以,在一定需求场景内使用。

实现

.img-wrapper{
    display: flex;
    flex-wrap: wrap;
    flex-direction: column;
    height: 1300px;
}

.img-wrapper>li{
    position: relative;
    width: calc(100% / 4);
    padding: 5px;
    box-sizing: border-box;
}
复制代码

我们对父容器设置弹性盒后,因为瀑布流是多行的所以还要flex-wrap设置wrap,并且flex-direction还要设置为column。最关键的是一定要设置一个高度。当然在其子元素用百分比设定要显示几列。

就这样,flex也可以实现一个瀑布流了。

当然,我们如果想改变一定程度的序列优先级,可以改变css的order属性。

.img-wrapper>li:nth-child(4n+1){
    order: 1;
}
.img-wrapper>li:nth-child(4n+2){
    order: 2;
}
.img-wrapper>li:nth-child(4n+3){
    order: 3;
}
.img-wrapper>li:nth-child(4n){
    order: 4;
}
复制代码

效果

  • [图片上传失败...(image-229309-1654092336814)]

可以看到flex瀑布流也实现了,而order可以一定程度上改变其顺序。不是像column必须按照先上下再左右去排列了。

优点

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

缺点

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

C. js+absolute方案

js实现瀑布流的话,我们可以考虑把子元素全部设置成绝对定位。然后监听图片加载,如果加载完就把子元素设置其对应的位置,逐个塞到父容器中。

实现

import Waterfall from "./js/Waterfall"
 window.onload = new Waterfall({
    $el: document.querySelector(".img-wrapper"),
    count: 4,
    gap: 10
})
复制代码

我们先在配置阶段把父容器和列数,间距设置好。

然后再去写Waterfall类:

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);
    }
}
复制代码

我们这里就简单实现了一下:

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

效果

  • [图片上传失败...(image-4e894f-1654092336814)]

我们这里可以看到这个瀑布流就比较符合我们的期望了,可以看到他初始的顺序变化是从左到右的。

优点

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

缺点

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

思考

我们已经说完了这三种方案,其实绝大部分的商业级都是js实现的瀑布流,比如isotope等插件。本质实现起来并不麻烦,我们有时间可以自己封装一套,在vue上怎么用,在react上怎么用,怎么过滤,怎么通过用关键词去排序改变顺序等等,都可以考虑进去。算是比较常见功能所以自己试试看也没坏处,自己动手丰衣足食么~

作者:jsmask
链接:https://juejin.cn/post/7014650146000470053
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 慢读《经济学原理》四:供给与需求的市场力量 尽量细致、通俗学习《经济学原理》,适合不想看太长原文又想学习经济学原理...
    梦境天堂阅读 3,411评论 0 1
  • 六一儿童节快乐呀!再不发公众号要被封号啦,所以我把之前写的碎碎念和名言名句啥的发在这存个档吧,嘻嘻嘻。 先来点励志...
    Ann璐璐阅读 1,715评论 0 1
  • 前阵子在写一个图片选择器时,想实现纯 CSS 对图片进行瀑布流式排版 (Masonry Layout)。一个合格的...
    追寻1989阅读 13,920评论 0 3
  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 14,694评论 1 92
  • 在六一之际,我和王垠银老师收到了孩子们给出的礼物。 昨天下午4点钟,我们去学校给孩子们上课,进入教室,教室里空无一...
    认真工作的开开阅读 2,615评论 0 2

友情链接更多精彩内容