Posts 四种瀑布流布局处理方案
Post
Cancel

四种瀑布流布局处理方案

等宽等高,等宽不等高,等高不等宽,既不等宽又不等高。

等宽等高

利用 inline-block 或者 float 的方式即可实现

等宽不等高

使用插件实现

直接调用 Masonry 库即可实现。

使用 JS 或 CSS 实现

js 的实现方法

1
首先需要确定网页应用的宽度和每个需要放置元素的宽度,通过计算可以得到布局的列数 C 。布局元素是从主容器的左上角开始依次向下放置,因此将左上角为坐标原点(0, 0),布局元素利用相对于主容器绝对定位的方式来确定位置。维护一个长度为 C 的数组 A,分别记录该列在垂直方向上的坐标,每放置一个元素时,遍历数组 A 得到垂直坐标最小数组元素的索引 i ,计算出坐标值并分别设置布局元素的 top 和 left 值,最后更新数组 A,将索引为 i 的数组元素的值加上布局元素的高度值。

css 的实现方法

1
对于不需要考虑 css3 兼容性的页面应用,可以直接利用 flexbox 布局或者 multi-columns 布局来实现。flexbox 布局的思路是将设置主容器属性 display: "flex",排列方向设置为 flex-direction: "row",每一列用一个容器包裹,并设置该容器属性 display: "flex",排列方向设置为 flex-direction: "column",最后设置列容器的宽度值即可得到想要的布局效果。multi-columns 布局是通过设置主容器的属性 column-count 的数目来分隔出对应的列数,然后每个布局元素设置属性 break-inside: "avoid" 来避免元素跨列,只需要简单的连个属性设置就可以实现基本的布局效果,另外还可以设置主容器的 column-gap 来调整每列之间的间距。

等高不等宽

等高不等宽严格意义来说是:每一行的每一张图片等高,并不是每行都等高。

1
js 实现的思路为:设定一个基准行高 H,每个图片元素的宽高比 R 乘以 H 然后累加,每次累加后的值 S 与主容器的宽度 W 做比较,若 S > W,则最后一张图片被放置到下一行, 之前已经累加的图片元素作为一个整体,进行等比拉伸,使其宽度等于主容器宽度,这样左右两边的元素都是对齐的,不会出现空白。若要在图片横向之间加入 margin 以区分不同的图片,则宽度 W 的值为主容器宽度减去所有横向 margin 值叠加的总和。

既不等宽也不等高

在网页上应用得不多,但是在 CSS-sprite 雪碧图上经常遇到,图片的排列尽可能的利用空间,不造成大图无谓的空白占用带宽。

有一个网格布局插件 react-grid-layout 可以允许用户随意拖拽内容,拖拽后就形成了这种既不等宽也不等高的布局,就这需要用到一种叫做 集装箱算法,处理很多小盒子放到一个大盒子里并竟可能少留空隙。

实现思路

这篇文档 Packing Lightmaps 提出一种区域划分的方法,随着矩形的不断的放入,区域也不断的拆分,过程如下图所示:

01.png

在主容器 R 里放入矩形 A 后,主容器区域被拆分为 A 右侧区域(Aright)和下侧区域(Abottom),放置矩形 B 时, 由于 B 的宽度大于 Aright 宽度, Abottom 区域被占用,同时矩形 B 将原 Abottom 拆分为 Bright 和 Bbottom,在放入矩形 C 时会先与 Aright 区域比较宽高,若空间不够,则依次比较 Bright、Bbottom,直到找到合适的空间,放置后也是按照规则拆分出右侧和下侧区域。

02.png

由上图可以看出,这是一个类似于一个构建二叉树的问题,每插入一个子叶的过程,都从根结点开始遍历,先从左子叶开始比较有没有合适的区域位置,若没有,再到右子叶开始比较,最终得到一个放置点。有了这个思路,就可以用 js 来实现它了:

实现代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
class BST {
  constructor(rects, width, height) {
    this.root = {
      w: width,
      h: height,
      x: 0,
      y: 0,
    };
    this.rects = rects;
    this.width = width;
    this.height = height;
    this.packedRects = [];
  }
  traversel(container, rect) {
    if (container.used) {
      return (
        this.traversel(container.right, rect) ||
        this.traversel(container.bottom, rect)
      );
    } else if (
      rect.w <= container.w &&
      (container.h === undefined ? true : rect.h <= container.h)
    ) {
      return container;
    } else {
      return null;
    }
  }
  splitContainer(container, rect) {
    Object.assign(container, {
      used: true,
      right: {
        w: container.w - rect.w,
        h: rect.h,
        x: container.x + rect.w,
        y: container.y,
      },
      bottom: {
        w: container.w,
        h: container.h === undefined ? undefined : container.h - rect.h,
        x: rect.x,
        y: container.y + rect.h,
      },
    });
  }
  packingRects() {
    this.rects.forEach((rect, index) => {
      const container = this.traversel(this.root, rect);
      if (container) {
        rect.x = container.x;
        rect.y = container.y;
        this.packedRects.push(rect);
        this.splitContainer(container, rect);
      }
    });
    return this.packedRects;
  }
}

/**
 *  @params rects  Array  eg: [{w: 2, h: 1}, {w: 1, h: 2}]
 *  @params width  Number eg:  3
 *  @params height Number eg: 100
 *  output Array eg: [{w:2, h:1, x: 0, y: 0}, {w: 1, h: 2, x: 2, y: 0}]
 */
function BinPacking2D(rects, width, height) {
  if (rects && width) {
    const packingInstance = new BST(rects, width, height);
    return packingInstance.packingRects();
  }
}
This post is licensed under CC BY 4.0 by the author.
Trending Tags
Contents

Trending Tags