Masonry瀑布流布局

Masonry瀑布流布局模块是基于grid布局的个性化布局方式,瀑布流布局将尽可能减少项目之间的间隙,并尽量增加可视项目数量,使得整个页面结构紧凑而饱满;瀑布流布局方向为从左至右从上至下,是为真实的瀑布流结构;Masonry支持自动更新内容高度和项目之间的关系,支持异步插入单元格内容。

前言

瀑布流布局实现方式有两种:

  1. 使用多列布局,主要通过column-countcolumn-gap样式实现,但是此方法的项目排列方式是:先“从上至下”,再“从左至右”,此种排列方式不太符合用户的阅读习惯
  2. 使用换行的弹性布局,主要通过flex布局和flex-flow: column wrap样式完成,但是这个方法同方法1一样是“从上至下”和“从左至右”排列
  3. 绝对定位+js实时计算,也是瀑布流鼻祖Pinterest网站使用的布局方式,但是此方法需要在项目插入列表的前后随时计算项目的宽高尺寸和position定位值,比较耗费内存资源

我们关注到grid-template-rows: masonry样式已经处于实验阶段(请点击这里),也就是说瀑布流布局未来可能会被纳入css标准,在某天将可以抛弃js,以纯css的方式创建瀑布流布局。

本框架的瀑布流布局是以grid布局为基础的,辅以简单的js而实现的,他的特点如下:

  • 整个容器以1px作为最小单位,项目高度和项目之间的间隙都是1px的整数倍
  • 通过修改项目的grid-row样式来实现自适应布局,不需要通过js计算
  • 根据项目内容多少自动更新单元格高度
  • 支持使用add方法和remove方法增加和删除一个或多个项目
  • 支持异步添加内容


简单使用

本模块需要遵循“父容器”=>“项目容器”=>“内容”的结构关系,可以ul/ol+li的形式构建节点。

在本例中第一层div是父容器,需将Masonry实例应用在这个容器上;section为项目容器,是用来计算和赋值grid-row样式的节点,用户的所有内容均需要放在section标签内。

  • 输出
  • HTML
  • 1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

  •                 
                    
                

或以new示例的方式运行模块。

  • 输出
  • HTML
  • JS
  • 1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

  •                 
                    
                
  •                 
                    new ax.Masonry('#demo01');
                    
                

使用参数content

初始化时,如果不把节点写在父容器内,那么可以将节点数组传入参数content

  • 输出
  • HTML
  • JS
  •                 
                    
                
  •                 
                    new ax.Masonry('#demo02',{
                        content:[...document.querySelector('#cont01').children]
                    });
                    
                

单元格间隙

单元格的间隙需要通过参数gap进行设置,该参数有两种写法:

  1. 字符串格式,构成格式:数字+单位,支持rempx单位,例如:gap:'1rem'或gap:'14px'
  2. 数字格式,例如:gap:20,表示间隙是20px
  • 输出
  • HTML
  • 1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

  •                 
                    
                

添加和清空节点

可以通过add方法加入一个或多个项目。该方法有两个参数,第一个参数为单个节点或节点数组;第二个参数为回调函数,回调函数支持一个参数即刚添加的节点数组。

clear方法将清空整个瀑布流列表,支持一个参数即回调函数,回调无参数。

  • 输出
  • HTML
  • JS
  •                 
                    
                
  •                 
                    let ins = new ax.Masonry('#demo03');
                    addone.onclick = ()=>{
                        let num = Math.floor(Math.random() * 10),
                            str = `${'<p></p>'.repeat(num)}${num}${'<p></p>'.repeat(num)}`,
                            item = ax.createEl('section','',str)
                        ins.add(item,(data)=>{console.log('新增项:',data)});
                    }
                    addten.onclick = ()=>{
                        let arr=new Array(10).fill(null).map(k=>{
                            let num = Math.floor(Math.random() * 10),
                                str = `${'<p></p>'.repeat(num)}${num}${'<p></p>'.repeat(num)}`;
                            return ax.createEl('section','',str);
                        });
                        ins.add(arr,(data)=>{console.log('新增项:',data)});
                    }
                    clear.onclick = ()=>{
                        ins.clear();
                    }
                    
                

删除节点

可以通过remove方法加入一个或多个项目。该方法有两个参数,第一个参数为单个节点或节点数组;第二个参数为回调函数,回调函数支持一个参数即刚删除的节点数组。

  • 输出
  • HTML
  • JS
  • 1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

  •                 
                    
                
  •                 
                    let ins = new ax.Masonry('#demo06');
                    removeone.onclick = ()=>{
                        ins.remove(ins.targetEl.firstElementChild,(data)=>{console.log('删除项:',data)});
                    }
                    removemore.onclick = ()=>{
                        ins.remove([ins.targetEl.children[0],ins.targetEl.children[1],ins.targetEl.children[2]],(data)=>{console.log('删除项:',data)});
                    }
                    
                

等待

addremoveclear方法有对应的等待事件,分别是b4Addb4Removeb4Clear

等待函数如果不是返回Promise将不会阻碍执行;如果返回的是Promise,但是没有resolve,将阻止执行;如果返回的是Promise而且获得了resolve,将不阻止执行。

  • 输出
  • HTML
  • JS
  •                 
                    
                
  •                 
                    let ins = new ax.Masonry('#demo11',{
                        b4Add:()=>{
                            return new Promise((resolve)=>{
                                if(confirm('确定添加么?')){
                                    resolve();
                                }
                            })
                        },
                        b4Remove:()=>{
                            return new Promise((resolve)=>{
                                if(confirm('确定删除么?')){
                                    resolve();
                                }
                            })
                        },
                        b4Clear:()=>{
                            return new Promise((resolve)=>{
                                if(confirm('确定清空么?')){
                                    resolve();
                                }
                            })
                        },
                    });
                    add01.onclick = ()=>{
                        let num = Math.floor(Math.random() * 10),
                            str = `${'<p></p>'.repeat(num)}${num}${'<p></p>'.repeat(num)}`,
                            item = ax.createEl('section','',str)
                        ins.add(item);
                    }
                    remove01.onclick = ()=>{
                        ins.remove(ins.targetEl.firstElementChild);
                    }
                    clearall.onclick = ()=>{
                        ins.clear();
                    }
                    
                

监听内容高度

项目内容增减会导致单元格高度变化,这个高度是被自动监听的,当内容发生变化导致单元格高度变化时将自动调整该单元格的grid-row样式。

当一个单元格尺寸发生了变化会连环导致其他单元格位置变化,这个变化将由浏览器内核自动处理,并自动调整列表项目之间位置关系,性能极高。

  • 输出
  • HTML
  • JS
  • 1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

  •                 
                    
                
  •                 
                    let ins = new ax.Masonry('#demo04'),
                        c01 = document.querySelector('#c01');
                    h01.onclick = ()=>{
                        c01.innerHTML = `<p></p><p></p><p></p>内容1<p></p><p></p><p></p>`;
                    }
                    h02.onclick = ()=>{
                        c01.innerHTML = `<p></p><p></p><p></p><p></p><p></p><p></p>内容2<p></p><p></p><p></p><p></p><p></p><p></p>`;
                    }
                    h03.onclick = ()=>{
                        c01.innerHTML = `<p></p><p></p>内容3<p></p><p></p>`;
                    }
                    
                

异步内容

图片瀑布流比较常见,我们以异步加载图片为例演示本模块的异步添加内容的方法。

异步内容之所以能自动调整高度,还是因为基于浏览器内核的grid自动排序算法,不管是在什么时机改变单元格内容,单元格的高度都将自动变化并重新调整与周围单元格的关系。

所以在往瀑布流列表添加新项目时,可以先插入单元格节点,再往单元格节点里异步添加内容;或者等内容准备好并填充了单元格节点,再将单元格加入到列表中。

  • 输出
  • HTML
  • JS
  •                 
                    
                
  •                 
                    let ins = new ax.Masonry('#demo05');
                    addpic.onclick = ()=>{
                        let num = Math.floor(Math.random() * 9) + 1,
                            src = `https://unpkg.com/@codady/resource/image/earth0${num}.jpg`,
                            img =new Image(),
                            item = ax.createEl('section','','<ax-spin></ax-spin>');
                        img.src = src;
                        img.onload = () => {
                            item.innerHTML = `<img src="${src}"/>`;
                        }
                        ins.add(item);
                    }
                    newpic.onclick=()=>{
                        let item = ins.targetEl.children[1];
                        if(item){
                            let pic = item.querySelector('img'),
                                num = Math.floor(Math.random() * 9) + 1;
                            pic.src = `https://unpkg.com/@codady/resource/image/earth0${num}.jpg`;
                        }
                    }
                    
                

自适应测试

由于需要计算内容的高度,而且grid-row属性值需要是正整数(grid-row:span 2.2被视为无效),我们将参数gap使用js控制,通过breakpoints断点设置多终端下的gap值。

而参数cols默认值为0,即表示grid-template-columns样式默认是由css控制的(默认值为4),用户可以通过设置css变量--_masonry-cols来改变不同屏幕尺寸下的列数。

当然我们可以将gapcols参数都通过breakpoints来进行终端设置。

演示示例
默认 平板竖 手机竖

布局配置

属性 类型 默认值 说明
gap string/number '0.8rem' 网格间隙尺寸
cols number 0 每行列数
classes string '' 目标节点样式类

数据源配置

属性 类型 默认值 说明
content string '' 数据源内容
contType 'text'/'html'/'node'/'image'/'async' 'text' 内容类型
contData object {} 请求数据参数
ajax object {} 异步请求配置

模板配置

属性 类型 默认值 说明
tplStr string '' 模板字符串
tplEng function null 模板引擎

媒体配置

属性 类型 默认值 说明
media.caption string '' 媒体文件名称
media.brief string '' 媒体文件简介

等待函数

属性 类型 默认值 说明
b4Add function null 添加项目前等待函数
b4Remove function null 删除项目前等待函数
b4Clear function null 清空节点前等待函数

回调函数

属性 类型 默认值 说明
onAdded function null 添加项目后回调
onRemoved function null 删除项目后回调
onCleared function null 清空项目后回调
onUpdatedCont function null 更新内容后回调