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来进行终端设置。

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