访问 Flickr 页面,会发现其中的图片布局非常美观。不同于我们平日所见的电商商品列表页,这里图片的大小可能完全不一致,但是 Flickr 却做到了错落有序,再加上其图片拍摄质量很高,给人一种非常美的视觉享受。此外,当我们调整页面宽度的时候,会发现页面自适应变化,图片会随着宽度调整重新排列,非常地流畅。其背后的原理是什么呢?

对该问题抽象后,便是:给定一系列图片大小,我们怎么给它们进行布局呢?

其实,Flickr 的技术博客上有提到这个问题。背后的思想非常简单:

给定一系列图片 $I^1 \cdots I^n$,假设其宽高分别为 $(W^i, H^i)$,如果我们需要将 m 张图片从左向右铺满一行,在其高度 h 固定的前提下,我们只需要保证

$$\sum_{i=1}^m \frac{h \cdot W^i}{H^i} = w$$

于是,h 就可以很容易计算出来:

$$h = \frac{w}{\sum_{i=1}^m \alpha^i}$$

其中,$\alpha^i=\frac{W^i}{H^i}$。这种情况下,甚至我们不需要知道图片的宽高,只需要有宽高比即可计算出一个合适的高度。

所以,在给定页面宽度的情况下,假设我们的每一行高度有一个范围,我们可以通过调整 m 的个数来计算出一个合适的 m,进而计算出每张图片所处的高度及相对左边界的距离。

于是,在给定一系列图片的情况下,我们可以从第一行开始,不断扩充这一行图片的数量,直到计算出的高度在合理范围内,则处理下一行。这一步,可以通过一个简单的循环达到目的。

伪代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let container_width = 1024; // 容器宽度
let aspects = [...]; // 宽高比列表
let index = 0;
let sum_as = 0, row = [];
let rows = [];
while (index < aspects.length) {
sum_as += aspects[index];
row.push(index);
let h = container_width / sum_as;
if (isValid(h)) {
sum_as = 0;
rows.push({
row,
height: h
});
row = [];
}
index += 1;
}

是不是很简单呢?

没错!对这个问题进行数学抽象后,其实就是一个小学数学题。数学就是这么神奇!

当然了,要实现一个类似于 Flickr 的完整的图片自适应布局代码,并不仅仅只是一分钟的数学推导,而需要我们好好优化代码(如像 Flickr 那样流畅的宽度实时自适应),这一点就属于工程能力了。