CSS 实现类似 Google Photos 的图片布局
今天在 medium 看到一篇关于 Google Photos 的文章,里面详细的介绍了谷歌的一名工程师是如何设计 Google Photos 的布局的,文章地址:Building the Google Photos Web UI。
观察了一下 Google Photos 的布局,发现它的布局非常的有意思,有以下特点:
- 一行图片的宽度始终刚好占满容 器宽度
- 图片全部保持原始比例,不会被拉伸
- 图片的高度不一,但是每一行的高度是一样的
- 图片顺序不会被改变
感觉很神奇,但是那篇文章里的实现方法太过复杂,虽然性能非常好,但是我觉得可以用更简单的方法来实现,于是就有了这篇文章。
我所设想的实现需要满足:
- 一行图片的宽度始终刚好占满容器宽度
- 除非比例十分极端,图片保持原始比例,不会被拉伸
- 图片的高度不一,但是每一行的高度是一样的
- 图片顺序不会被改变
- 前端纯 CSS 实现,不需要 JS
- 性能可以一般,但代码必须简单
实现
首先我在网上搜索了一下,发现有人已经思考过这个问题。比如这篇文章:使用纯 CSS 实现 500px 照片列表布局。虽然文中的做法可以实现,但是我还是觉得有点复杂。
经过一段时间思考,我想到一个使用暴力实现的方法,虽然不够优雅,但是实现起来非常简单,而且性能也不错。
实现思路主要依靠两点:object-fit
属性和 Flex 布局的 flex-grow
。
step 1: 初步可行
首先定义一个图片的初始最小宽度,比如 200px
,根据图片宽高比进行调整:
const initWidth = 200 * image.width / image.height;
这里使用了 JavaScript 计算,但实际上这部分计算应该在服务端进行,所以用什么语言实现都是一样的。 也并没有违背前端纯 CSS 实现的要求,因为这部分计算可以在服务端生成 CSS 代码,然后直接返回给浏览器。 看到后面的例子就会明白。浏览器直接禁用 JavaScript 也不会影响布局。
这样图片在不缩放的情况下是符合宽高比的,但是多张图片放到一行里不一定能沾满一行,需要进行调整。使父元素成为 Flex 容器,设置 flex-wrap: wrap
,这样图片就会自动换行了。然后设置图片的 flex-grow
属性,这样图片就会自动填充父元素的宽度。
这里的 flex-grow
的取值非常重要,必须让这一行的每一张图片在宽度增加的同时,高度继续保持一致。显然,如果每一张图片的 flex-grow
都是其自身的宽高比就行了。
const flexGrow = image.width / image.height;
下面我们总结一下,主要代码如下:
<div class="container">
<div class="image-box" style="width: var(--calc-form-server); flex-grow: ${--calc-form-server}">
<img width="calc-from-server" height="calc-from-server" class="image"/>
</div>
<div class="image-box" style="width: var(--calc-form-server); flex-grow: ${--calc-form-server}">
<img width="calc-from-server" height="calc-from-server" class="image"/>
</div>
<div class="image-box" style="width: var(--calc-form-server); flex-grow: ${--calc-form-server}"
<img width="calc-from-server" height="calc-from-server" class="image"/>
</div>
</div>
.container {
display: flex;
flex-wrap: wrap;
outline: 1px solid blue;
/* outline 仅用于观察 */
}
.image-box {
max-width: 100%;
outline: 1px solid green;
margin: 0.5rem;
/* outline 和 margin 仅用于观察 */
}
.image {
display: block;
height: 100%;
width: 100%;
outline: 1px solid red;
/* outline 仅用于观察 */
}