需求场景:
搜索结果是图片/视频列表.点击具体图片/视频弹框展示详情.
常见图片墙类型
- google 图片搜索
- 百度图片搜索
- 500px 专门的精美图片墙
碎碎念
最近几天反复查看图片墙列表的优化布局等方式.发现我们能做的很多,但是特定场景下又很少.总结一下不同的方式以及其优缺点.
总体需求目标(根据部分业务的场景)
- 符合人眼的浏览顺序(某种顺序)-强需求;
- 尽量不裁剪;
- 尽量最后一行铺满;
- 不强制要求每行高度相同,但行之间高度应该尽量趋于一致;
- 快速相应用户交互(滚动不出现卡顿)-强需求;
解决方案:
- 问题1中基本无论是js方案还是css方案都能满足需求,只跟服务器返回数据顺序相关;
- 问题5最关键的是减小图片体积.如果不做缩略图,单张图片3m*60 = 180m,对浏览器来说已经压力很大了.
- 问题2,4单独使用方案来说.
- 方案一
js 计算方案[justified-layout].输入图片宽高比,获取每一张图片位置.
优点:
1. 该算法最后实现靠的是有向无环图方式,计算速度快.
2. 背景框按照图片本身的宽高比实现,图片加载后页面不需要重新render.为了人眼适应,在快速滑动的时候给与一定背景色,图片不会闪瞎眼;
3. 图片完全不裁剪.
4. 无线滚动的话,可以快速定位到需要的位置(这里不展开讨论) 缺点:
- 需要监听浏览器的resize,每次resize手动重新布局;
- 分页展示时最后一行没得办法铺满;
-方案二 css 等高
实现方法
- 需要监听浏览器的resize,每次resize手动重新布局;
html:
<div className='item' key={item.id}> // 每一个img item.假设最外层是img-wrapper
<img src={img.url}/>
</div>
img-wrapper {
display: flex; // 保证图片flex排列
flex-wrap: wrap;// 保证图片排不下本行时折行
}
section::after {
content: '';
flex-grow: 999999999; // 防止最后一行不满一行时,通过flex-grow将图片放的过大
}
div {
 margin: 2px; // 边界
background-color: violet;
position: relative;
flex-grow:1 // 大家都伸展一下,把一行中空白的部分占满;
}
img {
height:180px; // 等高 很重要,没有这些值,图片下载的初期页面不知道宽高,会出现从0一下子渲染出现,简单来说就是闪瞎眼
top: 0;
width: 100%;
object-fit:cover;
}
最后结果:
- 无需手动计算,resize重排是浏览器自己的行为
- 其实已经能够满足大部分的需求了...
缺点
- 分页展示时最后一行没得办法铺满;
- object-fit据说有性能问题(存疑?待验证),因为等高,大部分图片都是被object-fit:cover裁剪过的;
-方案三
css 不等高实现方案
实现方法
<section>
<div ng-repeat="img in imgs" style="width:{{img.width*200/img.height}}px;flex-grow:{{img.width*200/img.height}}">//
<a style="padding-bottom:{{img.height/img.width*100}}%"></a>
<img src="{{img.url}}" alt="">
</div>
</section>
section {
display: flex;
flex-wrap: wrap;
}
section::after {
content: '';
flex-grow: 999999999;
min-width: 1000px; // 此处用来解决最后一行问题
height: 0
}
div {
margin: 2px;
background-color: violet;
position: relative;
/* flex-grow: 1; */此处将伸缩比值按照自身的宽度来同比伸缩
}
a{
display: block;
}
img {
position: absolute;
top: 0;
width: 100%;
vertical-align: bottom;
}
*注意
这里的图片给了一个预设高度200px;这是一个非常重要的数值.只有将所有图片的高度归一化为200,这时得到的宽度才方便浏览器的flex-wrap来计算折行的点.后续的伸缩其实是根据这个折行后的数据进行伸缩的.不同的高度折行的点不同,最后伸缩后的高度并不会脱离预设的这个值太多.这样看来,这个其实就是一种简单的寻找折行点的方法.比起第一种js的有相无环图看上去更快;
假如将高度预设为10px,得到的结果是:
<section>
<div ng-repeat="img in imgs" style="width:{{img.width*10/img.height}}px;flex-grow:{{img.width*10/img.height}}">
<i style="padding-bottom:{{img.height/img.width*100}}%"></i>
<img src="{{img.url}}" alt="">
</div>
</section>
- 纯css,resize是浏览器自身的行为;
- 不使用object-fit;图片没有裁剪;
- 每行的高度不相同,但大体上高度是接近的(取决于图片本身的长宽比在合理的范围);
缺点
- 末尾一行不能铺满;
- 对长宽比差距过大的不友好;
总结
- 解决快速响应问题必须方案:缩略图;可选方案:web worker后台加载;
- 解决resize问题:css方案;
- 解决裁剪问题:flex-grow:按照自身宽度所占比值
- 解决最后一行问题:无线滚动时,最后一行遮挡;分页时只是部分满足(设置section::after{min-height:500px,heigth:0})让最后的占位符在一定的宽度下,通过伸展剩余的图片(牺牲最后一行的高度与预设高度的差值)来填满整行.在小屏上表现还不错.
参考文档: