Masonry瀑布流布局
Masonry瀑布流布局模块是基于grid布局的个性化布局方式,瀑布流布局将尽可能减少项目之间的间隙,并尽量增加可视项目数量,使得整个页面结构紧凑而饱满;瀑布流布局方向为从左至右从上至下,是为真实的瀑布流结构;Masonry支持自动更新内容高度和项目之间的关系,支持异步插入单元格内容。
前言
瀑布流布局实现方式有两种:
- 使用多列布局,主要通过
column-count
和column-gap
样式实现,但是此方法的项目排列方式是:先“从上至下”,再“从左至右”,此种排列方式不太符合用户的阅读习惯 - 使用换行的弹性布局,主要通过
flex
布局和flex-flow: column wrap
样式完成,但是这个方法同方法1一样是“从上至下”和“从左至右”排列 - 绝对定位+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
进行设置,该参数有两种写法:
- 字符串格式,构成格式:数字+单位,支持
rem
和px
单位,例如:gap:'1rem'或gap:'14px' - 数字格式,例如: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)}); }
等待
add
、remove
和clear
方法有对应的等待事件,分别是b4Add
、b4Remove
和b4Clear
。
等待函数如果不是返回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
来改变不同屏幕尺寸下的列数。
当然我们可以将gap
和cols
参数都通过breakpoints
来进行终端设置。