浅谈flex算法

1,873 阅读3分钟

flex布局

flex布局,从网页端到移动端的weex和rn,基本都是根据flex白皮书整合的一套布局算法,刚好看到sprite-flex-layout这个库,用js实现的一套flex算法,刚好趁机了解一番。用法很简单,代码如下,container是父节点,node1、node2是子节点。

const { Node } = require('sprite-flex-layout')
const container = Node.create({
  width: 500,
  height: 500,
  flexDirection: 'row'
});
const node1 = Node.create({
  width: 200,
  height: 100
})
const node2 = Node.create({
  width: 100,
  height: 100
});

container.appendChild(node1);
container.appendChild(node2);

container.calculateLayout();
const layout = container.getAllComputedLayout();
console.log(layout);

控制台运行,输出如下, 可以看到父子节点的定位以及大小信息

{ left: 0,
  top: 0,
  width: 500,
  height: 500,
  children:
   [ { left: 0, top: 0, width: 200, height: 100 },
     { left: 200, top: 0, width: 100, height: 100 } ] }

具体实现

那话不多说,下面来看看这个库具体是怎么实现的。首先node1、node2会关联到container的节点children数组上,实现简单的父子关系的关联,这个没什么好说的。父节点container在调用calculateLayout方法的时候,会首先实例化Compose类,然后再调用Compose实例的compose方法去进行解析。flex算法在这里,也算是正式开始,我们盯住compose这两步就可以了

解析node节点配置

实例化Compose类的时候,主要做了三个步骤,第一是解析子节点的配置,即初始化节点是传入的配置,调用config.parse,从下面就可以看出,从边框的解析、padding解析等等之类的,根据配置还原我们节点原本的信息。其中像border、padding、margin之类的传入一维数组、二位数组、三维数组,会导致节点的布局效果跟我们平时在配置css的效果一样,而像flex、flexflow等等之类的配置,基本也是css的参数,只不过我们会在这里解析出赋值给节点。然后配置解析完,compose会根据子节点的order(优先 )或者id值,sort排序children的位置。

parse() {
    this.parseBorder();
    this.parsePadding();
    this.parseMargin();
    this.parseFlex();
    this.parseFlexFlow();
    this.parseFlexProps();
    this.parseSize();
    this.parseComputedWidth();
    this.parseComputedHeight();
    this.parseLayoutWidth();
    this.parseLayoutHeight();
  }

最后,我们会根据主轴是否需要换行或者主轴有没有配置大小,来决定children接触点是否需要换一个flexline,什么是flexline,当主轴是x轴的时候,我们可以说flexline是行,多个flexline就是多个行,这设计到交叉轴的计算。我们会把相同flexline的children都塞到同一个数组里面。当然如果里面设计到reverse属性,我们会把数组reverse之类的。

计算位置

compose实例化完成以后,调用compose方法就继续flex解析,这里的解析顺序是先解析交叉轴的位置,再解析主轴的位置,交叉轴的计算相对主轴来说比较复杂,因为交叉轴的位置,不仅设计到多个flexline的位置交叉轴调整问题,单个交叉轴flexline立面也有位置计算。flex计算主要分为下面三步:

compose() {
    this.parseAlignContent();
    this.parseAlignSelf();
    this.parseMainAxis();
    this.computeContainerSize();
 }
  1. 在parseAlignContent中,首先先从container中,得到交叉轴的大小crossAxisSize,然后再遍历叠加所有flexline的crossAxisSize得到linesCrossAxisSize,这个交叉轴的富余高度就是(space = crossAxisSize - linesCrossAxisSize),这样我们就可以根据交叉轴属性计算出每行之类的间距了。

  2. 然后parseAlignSelf中,要做的,也很简单,给每个flexline根据配置,重新计算每个flexline的布局。

  3. 最后是计算主轴的位置,parseMainAxis中的也是给每个flexline计算主轴的位置,也类似交叉轴的计算,首先计算出富余值space,再根据space大小,如果space大于0,则根据flex-grow重新配置,相反,如果小于0,需要根据flex-shrink去计算缩减值。

上面是三步计算完毕,每个子一个节点的位置和大小都计算出来了。

结语

这就是基本的flex算法,虽然还有一些属性没有支持,但是这并不妨碍我们对整个flex算法理解。