图片墙方案选型

901 阅读5分钟

需求场景:

搜索结果是图片/视频列表.点击具体图片/视频弹框展示详情.

常见图片墙类型
  1. google 图片搜索  
    google 图片搜索
  2. 百度图片搜索
  3. 500px 专门的精美图片墙

碎碎念

最近几天反复查看图片墙列表的优化布局等方式.发现我们能做的很多,但是特定场景下又很少.总结一下不同的方式以及其优缺点.

总体需求目标(根据部分业务的场景)

  1. 符合人眼的浏览顺序(某种顺序)-强需求;
  2. 尽量不裁剪;
  3. 尽量最后一行铺满;
  4. 不强制要求每行高度相同,但行之间高度应该尽量趋于一致;
  5. 快速相应用户交互(滚动不出现卡顿)-强需求;

解决方案:

  1. 问题1中基本无论是js方案还是css方案都能满足需求,只跟服务器返回数据顺序相关;
  2. 问题5最关键的是减小图片体积.如果不做缩略图,单张图片3m*60 = 180m,对浏览器来说已经压力很大了.
  3. 问题2,4单独使用方案来说.
  • 方案一  js 计算方案[justified-layout].输入图片宽高比,获取每一张图片位置.
    优点:
     1. 该算法最后实现靠的是有向无环图方式,计算速度快.
     2. 背景框按照图片本身的宽高比实现,图片加载后页面不需要重新render.为了人眼适应,在快速滑动的时候给与一定背景色,图片不会闪瞎眼;
     3. 图片完全不裁剪.
     4. 无线滚动的话,可以快速定位到需要的位置(这里不展开讨论)  缺点
    1. 需要监听浏览器的resize,每次resize手动重新布局;
    2. 分页展示时最后一行没得办法铺满;
      -方案二   css 等高 
      实现方法
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 {
&emsp;margin: 2px;&emsp;// 边界
  background-color: violet;
  position: relative;
  flex-grow:1&emsp;// 大家都伸展一下,把一行中空白的部分占满;
}

img {
  height:180px; // 等高&emsp;很重要,没有这些值,图片下载的初期页面不知道宽高,会出现从0一下子渲染出现,简单来说就是闪瞎眼
  top: 0;
  width: 100%;
  object-fit:cover;
}

最后结果:

优点

  1. 无需手动计算,resize重排是浏览器自己的行为
  2. 其实已经能够满足大部分的需求了...

缺点

  1. 分页展示时最后一行没得办法铺满;
  2. 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>

优点

  1. 纯css,resize是浏览器自身的行为;
  2. 不使用object-fit;图片没有裁剪;
  3. 每行的高度不相同,但大体上高度是接近的(取决于图片本身的长宽比在合理的范围);

缺点

  1. 末尾一行不能铺满;
  2. 对长宽比差距过大的不友好;

总结

  1. 解决快速响应问题必须方案:缩略图;可选方案:web worker后台加载;
  2. 解决resize问题:css方案;
  3. 解决裁剪问题:flex-grow:按照自身宽度所占比值
  4. 解决最后一行问题:无线滚动时,最后一行遮挡;分页时只是部分满足(设置section::after{min-height:500px,heigth:0})让最后的占位符在一定的宽度下,通过伸展剩余的图片(牺牲最后一行的高度与预设高度的差值)来填满整行.在小屏上表现还不错.

参考文档:

  1. medium.com/google-desi…
  2. github.com/xieranmaya/…
  3. github.com/flickr/just…