flex 布局进阶

299 阅读7分钟

flex布局进阶

盒模型发展史

内容属性和容器属性

在前端很早期的时候,只有块级盒子和内联盒子。

因此这个时候只需要进行一些比较简单的定义,规定一个块级标签会产生一个块级盒子,一个内联标签会产生一个内联盒子

随着前端的发展和进步,逐渐的产生了比如list-item和inline-block等盒子,因此原有的逻辑也就解释不清楚了,聪明的开发者巧妙的想出了一个解决办法,规定一个块级标签会产生2个块级盒子,一个是外在的盒子叫布局盒子,一个内在的盒子叫容器盒子

内容容器

定义: 放置子元素的地方

分类: 块级容器盒子,內联盒子

布局容器(外在盒子)

定义:规定元素之间是如何进行排布,始终环绕在内容容器的周围

分类: 块级布局盒子, 內联布局盒子

flex布局基础理论

那么上面了解容器和布局盒子有什么用呢?

我们的flex布局原来是作用在布局盒子

主轴

思考: 为什么flex布局需要定义主轴呢

解答: 首先我们在纸上写字的时候,书写方向是从左到右进行书写的,也可以做到右,在古代的书写顺序则是从上到下的书写顺序

因此我们在进行布局之前要考虑子元素排列的顺序.

定义: 规定元素的排列顺序的起止点

交叉轴

思考: 为什么flex布局需要交叉轴

解答: 我们在写文字的时候需要确定文字书写应该在纸的哪个位置进行开始书写, 哪个位置结束,

因此我们在进行布局之前要考虑子元素的开始位置以及结束位置

定义: 规定元素在父容器的起止位置

父容器

定义: 用来包裹目标元素的容器

容器属性: just-content, align-items, flex-wrap,flex-direction, align-content

用法: 参考阮一峰老师的文章

子容器

定义: 目标元素所在的容器

容器属性: align-self,order, flex-grow, flex-shrink, flex-basic

用法: 参考阮一峰老师的文章

flex布局实现

flex部分属性的js实现,以下js代码只是从实现的手段来升入理解学习flex,并不是意味着flex内部实

<div class="flex-container">
  <div class="flex-item">222</div>
  <div class="flex-item">222</div>
  <div class="flex-item">222</div>
  <div class="flex-item">222</div>
</div>

just-content

flex-start

定义: 规定了子元素在主轴上从主轴的起始位置向主轴的终点位置进行排列

 // 获取父容器元素
const flexItems = document.querySelectorAll('.flex-item');
// 获取子元素
 const flexContainer = document.querySelectorAll('.flex-container');

function flexStart(parentContainer) {
  const childrenContainer = parenContainer.childrenfor (const child of childrenContainer) {
    child.style.float = 'flet';
  }
}
flex-end

定义: 规定了子元素在主轴上从主轴的终点位置向主轴的起始位置进行排列

// 获取父容器元素
const flexItems = document.querySelectorAll('.flex-item');
// 获取子元素
 const flexContainer = document.querySelectorAll('.flex-container');

function flexEnd(parentContainer) {
  const childrenContainer = parenContainer.childrenfor (const child of childrenContainer) {
    child.style.float = 'right';
  }
}
space-between

定义: 规定了子元素在主轴上从主轴的起点位置向主轴的终点位置进行排列, 2端对齐进行排列

// 获取父容器元素
const flexItems = document.querySelectorAll('.flex-item');
// 获取子元素
 const flexContainer = document.querySelectorAll('.flex-container');

function spaceBetween(parentContainer) {
  const parentWidth = parentContainer.style.width;
  const firstChild = parentContainer.firstElementChild;
  const lastChild = parentContainer.lastElementChild;
  const childrenContainer = parenContainer.children;
  const length = childrenContainer.length;
  
  const childrenTotalWidth = calculateTotalWidth(childrenContainer);
  const parentResidueWidth = parentWidth - childrenTotalWidth;
  const residueUnitSpace = parentResidueWidth / length - 1;
  
  for (let i = 0; i < length; i++) {
    if ( i === 0) {
      firstChild.style.float = 'left';
    } else (i === length - 1) {
      lastChild.style.float = 'right';
      lastChild.style.marginLeft = `${residueUnitSpace}px`; 
    } else {
      const child = childrenContainer[i];
       child.style.marginLeft = `${residueUnitSpace}px`; 
       child.style.float = 'left'; 
    }
  }
}

// 计算子元素的布局容器的宽度
function calculateTotalWidth (children) {
  children = Array.isArray(children) ? children : Array.from(children);
  
  return children.reduce((prevTotalWidth, child) => {
    const style = child.style;
   return prevTotalWidth + style.offsetWidth + child.style.marginLeft + style.marginRight ;
  },0)
}
space-around

定义: 规定了子元素在主轴上从主轴的起点位置向主轴的终点位置进行排列, 每个子元素两侧的间隔相等。进行排列

// 获取父容器元素
const flexItems = document.querySelectorAll('.flex-item');
// 获取子元素
 const flexContainer = document.querySelectorAll('.flex-container');

function spaceAround(parentContainer) {
  const parentWidth = parentContainer.style.width;
  const firstChild = parentContainer.firstElementChild;
  const lastChild = parentContainer.lastElementChild;
  const childrenContainer = parenContainer.children;
  const length = childrenContainer.length;
  
  const childrenTotalWidth = calculateTotalWidth(childrenContainer);
  const parentResidueWidth = parentWidth - childrenTotalWidth;
  const residueUnitSpace = parentResidueWidth / length - 1;
  
  for (let i = 0; i < length; i++) {
      const child = childrenContainer[i];
      child.style.marginLeft = `${residueUnitSpace / 2}px`; 
      child.style.marginRight = `${residueUnitSpace / 2}px`; 
      child.style.float = 'left'; 
  }
}

// 计算子元素的布局容器的宽度
function calculateTotalWidth (children) {
  children = Array.isArray(children) ? children : Array.from(children);
  
  return children.reduce((prevTotalWidth, child) => {
    const style = child.style;
   return prevTotalWidth + style.offsetWidth + style.marginLeft + style.marginRight ;
  },0)
}

center

定义: 规定了子元素在主轴上从主轴的起点位置向主轴的终点位置进行排列, 第一项和最后一项的2边空白区域相等

// 获取父容器元素
const flexItems = document.querySelectorAll('.flex-item');
// 获取子元素
 const flexContainer = document.querySelectorAll('.flex-container');

function spaceAround(parentContainer) {
  const parentWidth = parentContainer.style.width;
  const firstChild = parentContainer.firstElementChild;
  const lastChild = parentContainer.lastElementChild;
  const childrenContainer = parenContainer.children;
  
  const childrenTotalWidth = calculateTotalWidth(childrenContainer);
  const parentResidueWidth = parentWidth - childrenTotalWidth;
  
  firstChild.style.marginLeft = parentResidueWidth / 2;
  lastChild.style.marginRight = parentResidueWidth / 2;

flex-grow

定义: 所有子元素的容器宽度之后如果小于父容器按照一定的规则进行分配

分配规则:

1、首先判断所有子元素的容器宽度之和是否小于父容器宽度

2、计算可剩余分配空间

3、计算每个子项应该分配多少空间

// 获取父容器元素
const flexItems = document.querySelectorAll('.flex-item');
// 获取子元素
 const flexContainer = document.querySelectorAll('.flex-container');

function flexGrow(parentContainer) {
  const parentWidth = parentContainer.style.width;
  const childrenContainer = parenContainer.children;
  const length = childrenContainer.length;
  
  const childrenTotalWidth = calculateTotalWidth(childrenContainer);
  const parentResidueWidth = parentWidth - childrenTotalWidth;
  const childrenTotalFlexGrow = calculateTotalFlexGrow(childrenContainer);
  const childrenAverageFlexGrow = parentResidueWidth / childrenTotalFlexGrow
  
  for (let i = 0; i < length; i++) {
      const child = childrenContainer[i];
      child.style.width = style.width + childrenAverageFlexGrow; 
  }
}

// 计算子元素的布局容器的宽度
function calculateTotalWidth (children) {
  children = Array.isArray(children) ? children : Array.from(children);
  
  return children.reduce((prevTotalWidth, child) => {
    const style = child.style;
   return prevTotalWidth + style.offsetWidth + style.marginLeft + style.marginRight ;
  },0)
}

// 计算子元素的布局容器的flexgrow之和
function calculateTotalFlexGrow (children) {
  children = Array.isArray(children) ? children : Array.from(children);
  
  return children.reduce((prevTotalFlexGrow, child) => {
    const style = child.style;
   return prevTotalFlexGrow + style.flexGrow;
  },0)
}

flex-shrink

定义: 所有子元素的容器宽度之后如果大于父容器按照一定的规则进行分配

分配规则:

1、首先判断所有子元素的容器宽度之和是否小于父容器宽度

2、计算缺少的空间

3、计算每个子项所占空间之和

4、计算每个子项的缩小单元

5、计算子项一共需要缩小多少

6、计算子项要缩小多少

思考: 如果我们按照类似和flexGrow一样分配空间,只是简单的进行用每个子项的宽度减去缺少的空间这样合理吗

就好比某一辆卡车本载了3中不一样重量的豆子,由于卡车现在超载了需要舍去一些豆子

我们需要计算出该怎么去舍去豆子, 首先我们需要计算出豆子占空间最大的那一份,

计算豆子占的总空间也就是豆子的缩小比例 * 豆子所占空间之和,

接下来计算当前种类的豆子占了多少空间 当前豆子的空间除以空间之和,也就是说当前豆子需要需要缩小的占比是这么这么多

多余的空间乘以 当前豆子需要缩小的占比得出的当前豆子需要缩小单元是多少

缩小单元乘以缩小比例也就是说缩小了多少

// 获取父容器元素
const flexItems = document.querySelectorAll('.flex-item');
// 获取子元素
 const flexContainer = document.querySelectorAll('.flex-container');

function flexGrow(parentContainer) {
  const parentWidth = parentContainer.style.width;
  const childrenContainer = parenContainer.children;
  const length = childrenContainer.length;
  
  const childrenTotalWidth = calculateTotalWidth(childrenContainer);
  const parentResidueWidth = parentWidth - childrenTotalWidth;
  const childrenTotalFlexGrow = calculateTotalFlexShrink(childrenContainer);
  const childrenTotalSpace = calculateTotalSpace(children);
  
  
  
  for (let i = 0; i < length; i++) {
      const child = childrenContainer[i];
      const style = child.style;
     const childSpace = parentResidueWidth * style.flexShrink;
     const childFlexShrink = style.width / childrenTotalSpace;
      child.style.width = childSpace * childFlexShrink; 
  }
}

// 计算子元素的布局容器的宽度
function calculateTotalWidth (children) {
  children = Array.isArray(children) ? children : Array.from(children);
  
  return children.reduce((prevTotalWidth, child) => {
    const style = child.style;
   return prevTotalWidth + style.offsetWidth + style.marginLeft + style.marginRight ;
  },0)
}

// 计算子元素的布局容器的flexShrink之和
function calculateTotalFlexGrow (children) {
  children = Array.isArray(children) ? children : Array.from(children);
  
  return children.reduce((prevTotalFlexGrow, child) => {
    const style = child.style;
   return prevTotalFlexGrow + style.flexShrink;
  },0)
}

// 计算子元素的所占空间的和是多少

function calculateTotalSpace (children) {
  children = Array.isArray(children) ? children : Array.from(children);
  
  return children.reduce((prevTotalFlexGrow, child) => {
    const style = child.style;
   return prevTotalFlexGrow + style.flexShrink * style.width;
  },0)
}
flex-basic

定义: 在分配多余空间之前,项目占据的主轴空间

function flexBasic(child) {
  child.style.width = child.style.flexBasic;
}

flex-wrap

定义: 项目都排在一条线,如果一条轴线排不下如何换行, 如果存在flex-wrap则不去计算flex-shrink以及flex-grow

// 获取父容器元素
const flexItems = document.querySelectorAll('.flex-item');
// 获取子元素
 const flexContainer = document.querySelectorAll('.flex-container');

function flexWrap (children, justContentCb) {
  const accommodateNumber = calcuateParentAccommodateNumber(parent, children);
  const length = children.length;
  const slice = Array.prototype.slice;
   let start = 0;
  let end = accommodateNumber;
  
  while (end < length && start <= end) {
    justContentcb?.(slice(start, end));
    start = end;
    end += accommodateNumber;
  }
}

function calcuateParentAccommodateNumber(parent, children) {
  let childrenTotalWidth = parent.style.width;
  
  for (let i = 0; i < children.length; i++) {
     const style = children[i].style;
    const childContainerWidth = style.offsetWidth + style.marginLeft + style.marginRight;
    
    if (childrenTotalWidth <= childContainerWidth) {
      return i;
    }
    
    childrenTotalWidth = childrenTotalWidth - childContainerWidth
  }
  
  return children.length;
}

flex-direction

定义: 规定主轴的方向

// 我们可以利用 css3提供的属性 writing-mode 进行修改瀑布流布局的方向
function flexDrection (parent) {
  prarent.style.writingMode = 'vertical-lr';
}

align-items

读者可以试着模拟 just-content去实现其所有属性的具体逻辑

align-content

读者可以试着自己去实现,实现方式可以参考flexwrap进行实现;

参考文献

阮一峰老师的flex布局

MDN中文参考文献

张鑫旭的博客