为了弄清楚几个现象,重新学习了 flex

flex 布局作为开发中的常规手段,使用起来简直不要太爽。其特点:

  • 相对于常规布局(float, position),它具备更高的灵活性;
  • 相对于 grid 布局,它具有更强的兼容性;
  • 使用简单,几个属性就能解决常规布局需求(当然 grid 布局也可以哈)

但是在开发使用 flex 布局的过程中,也会遇到一些自己难以解释的现象;通俗表述:为什么会是这样效果,跟自己想象的不一样啊?

那么针对自己提出的为什么,自己有去研究过?为什么是这样的效果?如何解决呢?

自己也存在同样的问题。所以最近有时间,重新学习了一遍 flex,发现自己对 flex 的某些属性了解少之又少,也就导致针对一些现象确实说不清楚。

下面我就针对自己遇到的几种疑惑现象进行学习,来对 flex 部分属性深入理解。

每天多问几个为什么,总有想象不到的意外收获 ---我的学习座右铭

回顾 flex 模型

flex 的基本知识和基本熟悉就不介绍了,只需要简单的回顾一下 flex 模型。

在使用 flex 布局的时候,脑海中就要呈现出清晰的 flex 模型,利于正确的使用 flex 属性,进行开发。

理解如下的几个概念:

  • 主轴(main axis)
  • 交叉轴(cross axis)
  • flex 容器(flex container)
  • flex 项(flex item)

main size 也可以简单理解下,后面内容 flex-basis 会涉及到。

还顺便理解一下 flex-item 的基本特点

  1. flex item 的布局将由 flex container 属性的设置来进行控制的。
  2. flex item 不在严格区分块级元素和行内级元素。
  3. flex item 默认情况下是包裹内容的,但是可以设置的高度和宽度。

现象一:flex-wrap 换行引起的间距

关键代码:

html 复制代码
<!-- css -->
<style>
  .father {
    width: 400px;
    height: 400px;
    background-color: #ddd;
    display: flex;
    flex-wrap: wrap;
  }
  .son {
    width: 120px;
    height: 120px;
  }
</style>
​
<!-- html -->
<body>
  <div class="father">
    <div class="son" style="background-color: aqua">1</div>
    <div class="son" style="background-color: blueviolet">2</div>
    <div class="son" style="background-color: burlywood">3</div>
    <div class="son" style="background-color: chartreuse">4</div>
  </div>
</body>

具体现象:

疑惑:为什么使用 flex-wrap 换行后,不是依次排列,而是排列之间存在间距?

一般来说,父元素的高度不会固定的,而是由内容撑开的。但是我们也不能排除父元素的高度固定这种情况。

排查问题 :针对多行,并且在交叉轴上,不能想到是 align-content 属性的影响。但是又由于代码中根本都没有设置该属性,那么问题肯定出现在该属性的默认值身上。

那么通过 MDN 查询:

align-content 的默认值为 normal,其解释是按照默认位置填充。这里默认位置填充到底代表什么呢,MDN 上没有明确说明。

但是在 MDN 上查看 align-items 时,却发现了有用的信息(align-items 是单行,align-content 是多行),normal 在不同布局中有不同的表现形式。

可以发现,针对弹性盒子,normal 与 stretch 的表现形式一样。

自己又去测试 align-content,果然发现 normal 和 stretch 的表现形式一样。那么看看 stretch 属性的解释:

那么只需简单的需改,去掉 height 属性,那么 height 属性默认值就为 auto。

html 复制代码
<!-- css -->
<style>
  .son {
    width: 120px;
    /* 注释掉 height */
    /* height: 120px */
  }
</style>

看效果:

可以发现,子元素被拉伸了,这是子元素在默认情况下应该占据的空间大小。

这里就需要理解 flex item 的特点之一:flex item 的布局将由 flex container 属性的设置来进行控制的

那么当子元素设置高度时,是子元素自己把自己的高度限制了,但是并没有改变 flex container 对 flex item 布局占据的空间大小,所以就会多出一点空间,也就是所谓的间距。

所以针对上面这个案例,换行存在间隔的现象也就理解了,因为第四个元素本身就排布在弹性盒子的正确位置,只是我们把子元素高度固定了,造成的现象是有存在间隔。

可以想一下,如果子元素的高度加起来大于父元素的高度,又是什么效果呢?可以自己尝试一下,看自己能够解释不?

现象二:flex item 拉伸?压缩?

在使用 flex 时,最常见的现象是这样的:

当子元素为 3 个时,不会被拉伸,为什么呢?

当子元素为 6 个事,会被压缩,又是为什么呢?

其实上面这两个疑问❓,只需了解两个属性:flex-growflex-shrink。因为这两个属性不常用,所以容易忽略,从而不去了解,那么就会造成疑惑。

flex-grow 属性指定了 flex 元素的拉伸规则。flex 元素当存在剩余空间时,根据 flex-grow 的系数去分配剩余空间。 flex-grow 的默认值为 0,元素不拉伸

flex-shrink 属性指定了 flex 元素的收缩规则。flex 元素仅在默认宽度之和大于容器的时候才会发生收缩,其收缩的大小是依据 flex-shrink 的值。flex-shrink 的默认值为 1,元素压缩

该两个属性都是针对 主轴方向的剩余空间

所以

  • 当子元素数量较少时,存在剩余空间,但是又由于 flex-grow 的值为 0,所以子元素宽度不会进行拉伸。
  • 当子元素数量较多时,空间不足,但是又由于 flex-shrink 的值为 1,那么子元素就会根据相应的计算,来进行压缩。

特殊场景: 当空间不足时,子元素一定会压缩?试试单词很长(字符串很长)的时候呢?

现象三:文本溢出,flex-basis?width?

在布局中,如果指定了宽度,当内容很长的时候,就会换行。但是会存在一种特殊情况,就是如果一个单词很长为内容时,则不会进行换行;跟汉字是一样的道理,不可能从把汉字分成两半。

那么在 flex 布局中,会存在两种情况:

可以发现:

  • 设置了固定的 width 属性,字符串超出宽度之后,就会截取。
  • 而设置了固定的 flex-basis 属性,字符串超出宽度之后,会自动扩充宽度。

其实在这里可能有人会有疑惑:为什么把 width 和 flex-basis 进行对比?或者说 flex-basis 这个属性到底是干什么?

其实我也是刚刚才熟悉到这个属性,哈哈哈,不知道吧!!!

因为 flex-basis 是使用在 flex item 上,而 flex-basis(主轴上的基础尺寸)属性在大多数情况下跟 width 属性是等价的,都是设置 flex-item 的宽度。

上面的案例就是属于特殊情况,针对单词超长不换行时,flex-basis 就会表现出不一样的形式,自动扩充宽度

简单学习一下 flex-basis 的基本语法吧。

flex-basis 属性值:

  • auto: 默认值 ,参照自身 width 或者 height 属性。
  • content: 自动尺寸,根据内容撑开。
  • <'width'>: 指定宽度。

当一个属性同时设置 flex-basis(属性值不为 auto) 和 width 时,flex-basis 具有更高的优先级

现象四:flex 平分

当相对父容器里面的子元素进行平分时,我们会毫不犹豫的写出:

css 复制代码
.father {
  width: 400px;
  height: 400px;
  background-color: #ddd;
  display: flex;
}
.son {
  flex: 1; /* 平分 */
  height: 90px;
}

那么我们是否会想过为什么会平分空间? 其中 flex:1 起了什么作用?

我们也许都知道 flex 属性是一个简写,是 flex-growflex-shrinkflex-basis 的简写。所以,flex 的属性值应该是三个组合值。

但是呢,flex 又类似于 font 属性一样,是一个很多属性的简写,其中一些属性值是可以不用写的,采用其默认值。

所以 flex 的属性值就会分析三种情况:一个值,两个值,三个值。

MDN 对其做了总结:

看图,规则挺多的,如果要死记的话,还是挺麻烦的。

针对上面的规则,其实只需要理解 flex 的语法形式,还是能够完全掌握(有公式,谁想背呢)。

scss 复制代码
flex = none | auto | [ <'flex-grow'> <'flex-shrink'>? || <'flex-basis'> ]  

希望你能看懂这个语法,很多 api 都有类似的组合。

  • | 表示要么是 none, 要么是 auto, 要么是后面这一坨,三选一。
  • || 逻辑或
  • ? 可选

理解上面这种语法之后,总结起来就是如下:

一个值

  1. none(0 0 auto)auto(1 1 auto) 是需要单独记一下的,这个无法避免。
  2. 无单位,就是 flex-grow,因为存在单位,就是 flex-grow 属性值规定为一个 number 类型的
  3. 有单位,就是 flex-basis,因为类似 width 属性是需要单位的,不然没有效果。

两个值

  1. 无单位,就是 flex-grow 和 flow-shrink,理由如上
  2. 其中一个有单位,就是 flex-grow 和 flex-basis,因为 flex-shrink 是可选的(这种情况是没有任何实际意义的,flex-basis设置了根本无效)。

三个值

三个值不用多说,一一对应。

理解了上面的语法形式,再来看 flex: 1 的含义就轻而易举了。一个值,没有单位,就是 flex-grow,剩余空间平均分配

现象五:多行,两边对齐布局

无论是 app 开发,还是网页开发,遇到最多的场景就是这样的:

两边对齐,一行元素之间的间距相同;如果一行显示不下,就换行依次对齐排布。

那么不难想到的就是 flex 布局,会写下如此代码:

css 复制代码
.father {
  display: flex;
  justify-content: space-between;
  flex-wrap: wrap;
}
.son {
  width: 90px;
  height: 90px;
}

那么你就会遇到如下情况:

其中的第二、三种情况布局是不可以接受,数据数量不齐的问题。但是数据是动态的,所以不能避免出现类似情况。

你们遇到过这种类似的布局吗?会存在这种情况吗?是怎么解决的呢?

第一种解决方案:硬算

不使用 flex 的 justify-content 属性,直接算出元素的 margin 间隔。

css 复制代码
.father {
  width: 400px;
  background-color: #ddd;
  display: flex;
  flex-wrap: wrap;
}
.son {
  margin-right: calc(40px / 3); /* 40px 为 父元素的宽度 - 子元素的宽度总和,    然后平分剩余空间*/
  width: 90px;
  height: 90px;
  background-color: #5adacd;
  margin-bottom: 10px;
}
/* 针对一行最后一个,清空边距 */
.son:nth-child(4n) {
  margin-right: 0;
}

缺点:只要其中的一个宽度发生变化,又要重新计算。

第二种解决方案:添加空节点

为什么要添加空节点呢?因为在 flex 布局中,没有严格的区分块级元素和行内元素。那么就可以使用空节点,来占据空间,引导正确的布局。

html 复制代码
<style>
  .father {
    width: 400px;
    background-color: #ddd;
    display: flex;
    justify-content: space-between;
    flex-wrap: wrap;
  }
  .son {
    width: 90px;
    height: 90px;
    background-color: #5adacd;
    margin-bottom: 10px;
  }
  
  /* height 设置为 0 */
  .father span { 
    width: 90px; /*空节点也是 flex-item, width 是必须一致的,只是设置高度为0,不占据空间*/
  }
</style>
</head>
<body>
  <div></div>
  <div class="father">
    <div class="son">1</div>
    <div class="son">2</div>
    <div class="son">3</div>
    <div class="son">4</div>
    <div class="son">5</div>
    <div class="son">6</div>
    <div class="son">7</div>
    <!-- 添加空节点,个数为 n-2 -->
    <i></i>
    <i></i>
  </div>
</body>

这样也能解决上面的问题。

添加空节点的个数:n(一行的个数) - 2(行头和行尾,就是类似第一种情况和第四种情况本身就是正常的,就不需要空间点占据)

缺点:添加了dom节点

上面两种方案都解决问题,但是都有着各自的缺点,具体采用哪种方式,就看自己的选择了。

那么你们还有其他的解决方案吗?

总结

其实本篇所解释的现象是自己对 flex 知识掌握不牢而造成的,从而记录此篇,提升熟悉度。也希望能够帮助对这些现象有困惑的码友。

如果存在错误解释,评论区留言。

相关推荐
神夜大侠2 小时前
VUE 实现公告无缝循环滚动
前端·javascript·vue.js
明辉光焱2 小时前
【Electron】Electron Forge如何支持Element plus?
前端·javascript·vue.js·electron·node.js
柯南二号2 小时前
HarmonyOS ArkTS 下拉列表组件
前端·javascript·数据库·harmonyos·arkts
wyy72932 小时前
v-html 富文本中图片使用element-ui image-viewer组件实现预览,并且阻止滚动条
前端·ui·html
前端郭德纲2 小时前
ES6的Iterator 和 for...of 循环
前端·ecmascript·es6
王解2 小时前
【模块化大作战】Webpack如何搞定CommonJS与ES6混战(3)
前端·webpack·es6
欲游山河十万里2 小时前
(02)ES6教程——Map、Set、Reflect、Proxy、字符串、数值、对象、数组、函数
前端·ecmascript·es6
明辉光焱2 小时前
【ES6】ES6中,如何实现桥接模式?
前端·javascript·es6·桥接模式
PyAIGCMaster3 小时前
python环境中,敏感数据的存储与读取问题解决方案
服务器·前端·python
baozhengw3 小时前
UniAPP快速入门教程(一)
前端·uni-app