最近爬了一些漂亮的小姐姐的图片,做成一个小网站妹子图-魔力美少女 ,专门孝敬给各位码农大哥。网站使用瀑布流布局,简单总结经验如下:
什么是瀑布流?
Pinterest Masonry 瀑布流布局是一种常见的网页布局方式,常见于图片类网站。看两个例子:
妹子图 - Bing images
小红书
它的特点是每个元素的宽度相同,但是高度不同,且元素之间的高度差异较大。大家厌烦了普通的网格布局,而 Pinterest Masonry 瀑布流让元素有错位的感觉,看起来更加有趣。
当然,这种布局现在也被广泛应用在各种网站上,也造成了审美疲劳。任何创意都有其适用范围,最好的布局还是因地制宜,不要盲目追求创意。
比如,图片类型的网站,依然非常适合 Pinterest Masonry 瀑布流布局。因为图片的大小不一,而且图片之间的高度差异较大,这种布局就非常适合。而视频类的网站,因为视频的长宽比一般固定,自然就也不需要此布局。
为什么叫瀑布流(无限滚动)?
老外把这种布局叫做 Masonry,是“彻砖”的意思,侧重于该布局的静态表现。
中文把这种布局叫做 “瀑布流”,侧重于强调该布局的动态效果。指网页向下滚动,数据自动加载,然后页面高度增长,也称作无限滚动(infinite scroll) 。
接下来介绍几种 Masonry 布局的几种实现方案,并陈述其利弊。
最省事的方案 columns
columns 是纯CSS方案,不需要JS,也不需要任何库。只需要在父元素上设置 column-count 属性,然后在子元素上设置 break-inside 属性即可(break-inside 也可以不用设置)。
columns:用于设置元素的列宽和列数。它是column-width和column-count的简写属性。
结构如下 (HTML):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
< div class = "container" >
< div class = "item" style = "height: 140px" ></ div >
< div class = "item" style = "height: 190px" ></ div >
< div class = "item" style = "height: 170px" ></ div >
< div class = "item" style = "height: 120px" ></ div >
< div class = "item" style = "height: 160px" ></ div >
< div class = "item" style = "height: 180px" ></ div >
< div class = "item" style = "height: 140px" ></ div >
< div class = "item" style = "height: 150px" ></ div >
< div class = "item" style = "height: 170px" ></ div >
< div class = "item" style = "height: 170px" ></ div >
< div class = "item" style = "height: 140px" ></ div >
< div class = "item" style = "height: 190px" ></ div >
< div class = "item" style = "height: 170px" ></ div >
</ div >
CSS 如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
. container {
max-width : 600 px ;
margin : 20 px auto ;
columns : 4 ;
gap : 20 px ;
counter-reset : items ;
}
. item {
border-radius : 3 px ;
background-color : #a1cbfa ;
border : 1 px solid #4290e2 ;
color : #fff ;
padding : 15 px ;
margin-bottom : 10 px ;
box-sizing : border-box ;
/*防止元素内部分离而导致出现在两列*/
break-inside : avoid ;
}
/* item排序 */
div . item :: before {
counter-increment : items ;
content : counter ( items );
}
效果如下:
这种方式可以简单快速实现砖墙布局,几近完美。有两个瑕疵:
一个是子元素是从上到下排列,这个是下一个问题的源头。
一般而言,砖彻上去之后,理论上是不地动的,在上面添加新的砖,只会增加墙的高度,而不会重新排列旧的砖头的位置。而这种方式,添加新的元素,会导致旧的元素重新排列,这是不符合砖墙布局的特性的。
例如,上面的例子,新增加砖头之后,那么,旧的砖头,会重新排列,这是不符合砖墙布局的特性的。
原本第4、5 块砖头,是在第2列的,但是,当新增加砖头之后,第4、5块砖头,就会被移动到第1列,这是不符合现实情况,当然,网页世界也不用完全对应现实世界。但是,这种方式会导致浏览过的内容,反复出现在不同的位置,这是不符合用户的预期的。
Grid Masonry
这种方式是通过设置row的高度,来实现瀑布流布局的。 原理如下:
. grid {
display : grid ;
grid-template-columns : repeat ( auto - fill , minmax ( 250 px , 1 fr ));
grid-gap : 1 rem ;
}
横向的图片设置成short, 纵向的图片设置成tall
. short {
grid-row : span 1 ;
}
. tall {
grid-row : span 2 ;
}
这种方式,可以实现瀑布流布局的特性,但是,这种方式,需要手动计算每个砖的高度,然后设置row的高度,这样做,会导致砖墙布局的实现,和砖的高度,有很大的关系,如果砖的高度,发生了变化,那么,砖墙布局的实现,也会发生变化。
flexbox 方案
CSS masonry with flexbox, :nth-child(), and order | Tobias Ahlin
flexbox 通过设置子元素的 order 属性,来实现瀑布流布局的。原理如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/* Render items as columns */
. container {
display : flex ;
flex-flow : column wrap ;
}
/* Re-order items into rows */
. item : nth-child ( 3n + 1 ) { order : 1 ; }
. item : nth-child ( 3n + 2 ) { order : 2 ; }
. item : nth-child ( 3n ) { order : 3 ; }
/* Force new columns */
. container :: before ,
. container :: after {
content : "" ;
flex-basis : 100 % ;
width : 0 ;
order : 2 ;
}
效果如下:
flex-flow 是 flex-direction 和 flex-wrap 组合的简写属性 。第一个指定的值为 flex-direction ,第二个指定的值为 flex-wrap.
这种方式非常的巧妙,而且让元素看上去是从左到右横向排列的(从左到右,符合预期)。这个方案算得上真正的完美,唯一的瑕疵就是代码量大。如果要做成响应式,适配不同的窗口大小,那css的代码量会更大一点。
JavaScript 方案
以上CSS的方案各有优缺点,也存在浏览器兼容性问题,要想真正实现 Masonry 布局,还得引入 JavaScript,推荐两实用的类库:
其原理原理 大致为:
写一个分列的方法 generateMasonryGrid(columns_count,posts)
网页 resize 时再重新分列
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
const container = document . querySelector ( '.container' );
function generateMasonryGrid ( columns , posts ){
container . innerHTML = '' ;
let columnWrappers = {};
for ( let i = 0 ; i < columns ; i ++ ){
columnWrappers [ `column ${ i } ` ] = [];
}
for ( let i = 0 ; i < posts . length ; i ++ ){
const column = i % columns ;
columnWrappers [ `column ${ column } ` ]. push ( posts [ i ]);
}
for ( let i = 0 ; i < columns ; i ++ ){
let columnPosts = columnWrappers [ `column ${ i } ` ];
let div = document . createElement ( 'div' );
div . classList . add ( 'column' );
columnPosts . forEach ( post => {
let postDiv = document . createElement ( 'div' );
postDiv . classList . add ( 'post' );
let image = document . createElement ( 'img' );
image . src = post . image ;
let hoverDiv = document . createElement ( 'div' );
hoverDiv . classList . add ( 'overlay' );
let title = document . createElement ( 'h3' );
title . innerText = post . title ;
hoverDiv . appendChild ( title );
postDiv . append ( image , hoverDiv )
div . appendChild ( postDiv )
});
container . appendChild ( div );
}
}
let previousScreenSize = window . innerWidth ;
window . addEventListener ( 'resize' , () => {
imageIndex = 0 ;
if ( window . innerWidth < 600 && previousScreenSize >= 600 ){
generateMasonryGrid ( 1 , posts );
} else if ( window . innerWidth >= 600 && window . innerWidth < 1000 && ( previousScreenSize < 600 || previousScreenSize >= 1000 )){
generateMasonryGrid ( 2 , posts );
} else if ( window . innerWidth >= 1000 && previousScreenSize < 1000 ){
generateMasonryGrid ( 4 , posts )
}
previousScreenSize = window . innerWidth ;
})
if ( previousScreenSize < 600 ){
generateMasonryGrid ( 1 , posts )
} else if ( previousScreenSize >= 600 && previousScreenSize < 1000 ){
generateMasonryGrid ( 2 , posts )
} else {
generateMasonryGrid ( 4 , posts )
}
我的方案
我写的妹子图主要使用了columns的方案,这样简单省事。除了做滚动加载之外,我还在前面也添加了“加载更多”的按钮。同时创造性的数据前插
,一定程度上让图片的展示更加凌乱。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
useEffect (() => {
if ( ! isFetching ) return ;
let url = `/api/page/ ${ page } ` ;
setTimeout (() => {
fetch ( url )
. then (( res ) => res . json ())
. then (( data ) => {
setIsFetching ( false );
// 数据往前插
setPosts ([... data , ... posts ])
});
}, 1000 );
}, [ isFetching , page , posts ])
未来的方案
CSS Grid Layout Module Level 3
目前 CSS 布局模块Level 3已经进入到 ED(Editor’s Draft)阶段,该规范为 grid 添加了一个名为 masonry
值,可以轻松的实现瀑布流。届时,浏览器布局将消耗更多的计算性能。
参考资料