自适应照片墙布局

最近,业务上对照片处理的需求比较多,其中有一个照片瀑布流的效果,类似于500px,实现图片自适应的等高、无拉伸、无剪裁显示。大概效果如下图所示。

开始以为会有点好玩的算法,通过js简单控制图片的排列,后来发现,在已知每幅图的宽高的情况下,只需css即可实现这样的效果。
cssphotolayout

核心代码如下所示:

1
2
3
4
5
6
7

<div id="app">
<div class="img-wrap" v-for="item in imgs" :style="`width: ${item.width/item.height * 100}px; flex-grow: ${item.width/item.height * 100}`">
<i :style="`padding-bottom: ${item.height/item.width * 100}%`"></i>
<img :src="item.url">
</div>
</div>

其中css为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#app {
display: flex;
flex-wrap: wrap;
}
.img-wrap {
margin: 2px;
background-color: violet;
position: relative;
}
i {
display: block;
}
img {
position: absolute;
top: 0;
width: 100%;
}

接下来是原理。

首先高度要一致

首先,看下最后的实现结果可以知道,每一行的高度要求持平,也就是说,在同一行中,所有图片的高度要求缩放到一致。那么,我们不妨设某一行的图片高度为100px。此时,该行图片中的某一张的已知真实宽高分别为wh。此时,如果将h缩放至100px,那么可以知道,该图片的宽度应该为100w/h。也就是说,此时,承载这张图片的容器的样式应为

1
2
3
<div class="img-wrap" v-for="item in imgs" :style="`width: ${item.width/item.height * 100}px;">

</div>

其次子元素撑开图片容器

这里先提一下,当padding以及margin的值为百分比的时候,其实是相对于父级width的。也就是说,在业务中,很多情况下一些自适应的正方形块,我们可以通过子元素padding-bottom: 100%,意思是高度是父元素宽度的100%,这样就将父级元素撑成一个正方形。

而此时,并不是所有图片都是正方形,但是,我们已知图片的原有宽高,那么宽高的比例也是已知的,分别为wh,那么也就是说,高度h是宽度wh/w %。也就是说,我们只需将一个子元素的padding-bottom设置为h/w %,即可得到一个满足图片的原有比例图片容器。那么,此时图片的容器样式应该为

1
2
3
<div class="img-wrap" v-for="item in imgs" :style="`width: ${item.width/item.height * 100}px;">
<i :style="`padding-bottom: ${item.height/item.width * 100}%`"></i>
</div>

那么这时候就会问了,开始咱们不是定了高度为100px么?

最后两端对齐

再看一眼我们最终要实现的结果,不但要求底部水平对齐,还要求两端对齐。用过flex就知道,只需将父元素设置为display: flex,子元素设置flex-grow: x即可。这时,x应该是多少呢?要知道,flex-grow是按照此比例,分配剩余的空间。又因为每个图片的大小是不一样的,所以,这个flex-grow的值当然是不一样的。细心的我们可以发现,其实比例,只需按照自身的宽度分配即可,也就是说,flex-grow:w/h*100,正所谓,胖子多得。。

那么问题就来了,宽度已经被flex-grow了,那么高度怎么办?高度还是100px么?显然不是了,但是看下上一小节中,我们对子元素的处理,用来撑开图片容器的子元素<i>的高度,也就是它的padding-bottom,是根据图片容器的宽度自适应的!那么,上一节的最后个疑问是不是已经打消了?

换行

换行就比较简单了,父元素设置flex-wrap: wrap即可。最终的代码完全体,就和开始一样。

最后

回想开始的时候,我们假设的高度100px,这个值是可以根据自身的需要去简单调整的,这个初始值确定了之后,后续的flex-grow会在100px左右伸缩。