起因
flex布局想必大家都已经用了很久了,element的el-table大家也应该用过,最近公司的一个项目中有一个基于flex布局 + el-table的高度自适应的问题让我又重新学习了一遍flex。
问题描述
页面的整体布局大概如下
其中页面头部和导航区域是固定的,然后整体页面是通过flex布局做到大小的自适应。表格搜索区域因为有高级搜索,所以它的高度是不固定的。表格内容区域使用了一个el-table并且height设置为了100%,让他也进行了高度自适应。
然而在实际过程中,当浏览器的高度变小或者表格搜索区域变大(压缩了表格内容区域的空间),就会导致出现双滚动条的情况出现,并且滚动条会出现莫名动画。
说了这么多,把大致的代码放出来看下
复习一下flex布局
flex布局--阮一峰,阮一峰老师的亲情讲解。
看完之后,我们知道父元素(容器)如果指定了display:flex,那么子元素(项目)就会按照主轴(row)或者交叉轴(column)的方向排列。
以纵向为例,如果有三个子元素,指定了前后两个子元素的宽度,中间的子元素如果指定了flex:auto,那么容器剩余的空间就会被中间元素占据。
<div class="parent">
<div class="child1"></div>
<div class="child2"></div>
<div class="child3"></div>
</div>
<style>
.parent {
height:100px;
width:500px;
display:flex;
}
.child1 {
width:100px;
background:red;
}
.child2 {
flex:auto;
background:yellow;
}
.child3 {
width:100px;
background:blue;
}
</style>
可以看到,child2占据了剩余的300px的像素大小。为什么flex:auto就可以做到呢。其实是因为flex是一下三种属性的缩写
- flex-grow:代表子元素基于剩余空间的放大比例,默认是0,表示即使有剩余空间也不放大。
- flex-shrink:表示子元素在空间不足时的缩小比例,默认是1,表示如果空间不足,子元素会按比例缩小。如果为0表示关闭。
- flex-basis:表示子元素在分配多余空间之前所占据主轴/交叉轴空间的大小,默认值是auto,表示项目原本的大小。 flex取值有三种:
- flex:auto => flex: 1 1 auto;
- flex:1 => flex: 1 1 0%;
- flex:none => flex: 0 0 auto; 上面我们定义了flex:auto,即定义了flex-grow为1,所以child2默认占据了剩余的300px像素。
那么如果child2定义了像素,且像素大于300px像素(比剩容器余像素大),会出现什么情况呢? 如果我们给child2做如下样式改变
.child2 {
width:800px;
background: yellow;
}
具体算法如下:
容器总体宽度为500px,实际所有子元素宽度总和为1000px(100+800+100),child1和child3占据100/1000*500 => 50px,child2占据800/1000*500 => 400px。
此时如果child1和child3设置了flex-shrink:0,会出现上面情况呢?
由于child1,child3都设置了不支持缩放,所以他们不会被挤压,剩余空间不变,child2还是300px。
子元素撑开父元素
以上的所有例子都是不涉及项目存在子元素的情况下,只是单纯用width规定了项目的大小。下面看一个例子,当项目中存在子元素,且子元素的宽度超出了容器的宽度。
<div class="container">
<div class="left"></div>
<div class="right">
<div class="content"></div>
</div>
</div>
<style>
.container {
height:200px;
width:500px;
display: flex;
border:1px solid #000;
}
.left {
width:200px;
background: lightcoral;
}
.right {
flex:auto;
display: flex;
}
.content {
flex:auto;
width:600px;
background: lightblue;
}
</style>
如图所示,content的宽度撑开了right,导致right的宽度大于了container的宽度500,所以right侵占了left的空间。此时需要给left指定flex-shrink:0。
此时left的宽度没有被占据,right的宽度依然被撑开。在实际开发过程中,我们肯定不想出现这种情况。
下面我们给right设置一个overflow-x:auto,给溢出部分设置滚动条。
.right {
flex:auto;
display: flex;
overflow:auto;
}
这时你会惊奇的发现,right的width变成了300px,没有被content撑开宽度,而且也没有出现滚动条。当你以为这就完事了的时候,你会发现还是年轻了。这只是在content没有内容的情况下,当content有内容时,会是下面的情况。
<div class="content">
<div class="inner"></div>
</div>
<style>
.content {
flex:auto;
width: 600px;
background: lightblue;
}
.inner {
height:100px;
width:700px;
background: burlywood;
}
</style>
宽度虽然没有被撑开,但是超出部分会导致大量留白。此时我们还需要给content设置overflow-x:auto。
你以为这就结束了?那你又年轻了。看一个更加接近实际的例子。
<div class="container">
<div class="left"></div>
<div class="right">
<div class="content">
<div class="content--header"></div>
<div class="content--body"></div>
<div class="content--footer"></div>
</div>
</div>
</div>
<style>
.container {
margin:50px;
height:500px;
width:500px;
display: flex;
border:1px solid #000;
}
.left {
width:200px;
background: lightcoral;
flex-shrink:0;
}
.right {
flex:auto;
display: flex;
overflow-x:auto;
}
.content {
flex:auto;
overflow-x: auto;
display: flex;
flex-direction: column;
}
.content--header {
height: 100px;
background: #e4393c;
}
.content--body {
flex: auto;
width:700px;
background: burlywood;
}
.content--footer {
height: 100px;
background: lightblue;
}
</style>
惊不惊喜,意不意外。当存在兄弟元素的时候,我胡汉三又回来了。
彻底解决这个顽疾需要在content外部再套一层div。
以上这种操作我称之为
100% + auto解决方案,即外部定义宽度/高度,然后内部元素继续使用auto。这样就不会导致因为auto造成的溢出问题。
反之,如果外部定义flex:auto,内部元素使用100%,也是一样的效果。
el-table
element的el-table,他的height属性可以给表格设置高度,当表格区域内容高度大于设置的height时,就会出现表格滚动条。如果将height设置为100%,那么他就会默认填充父元素的高度,当浏览器/父元素的高度变小时,会压缩.el-table的高度,导致高度溢出,从而出现双滚动条。
知乎上有一篇文章,el-table表头固定自适应高度解决方案
他是通过变量tableHeight来给el-table设置height,并监听onresize事件来重新计算tableHeight。这样可以保持el-table的高度始终不会被撑开,就不会出现双滚动条的情况
但是通过js的方式来计算,不去考虑性能损耗(重排重绘),也是一件很麻烦的事情。我们能不能简单通过html+css的方式来实现去除双滚动条呢?
<template>
<div class="container">
<div class="header" :class="{ large: large }" @click="toggle">
页面头部,高度{{ large ? 200 : 100 }}px
</div>
<div class="main">
<!--此处省略el-table其他参数-->
<el-table height="100%"></el-table>
</div>
</div>
</template>
<script>
export default {
data() {
return {
large:false
}
},
methods: {
toggle() {
this.large = !this.large;
}
}
}
</script>
<style lang="scss" scoped>
.container {
font-size: 20px;
height: 100%;
display: flex;
flex-direction: column;
.header {
height: 100px;
background: lightcoral;
flex-shrink: 0;
&.large {
height: 200px;
}
}
.main {
flex: auto;
}
}
</style>
上面例子中,当我们页面头部变大时,会导致出现双滚动条的情况
下面我们修改一下页面结构
<template>
<div class="container">
<div class="header" :class="{ large: large }" @click="toggle">
页面头部,高度{{ large ? 200 : 100 }}px
</div>
<div class="main">
<div class="table-wrap">
<!--此处省略el-table其他参数-->
<el-table height="100%"></el-table>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
large:false
}
},
methods: {
toggle() {
this.large = !this.large;
}
}
}
</script>
<style lang="scss" scoped>
.container {
font-size: 20px;
height: 100%;
display: flex;
flex-direction: column;
.header {
height: 100px;
background: lightcoral;
flex-shrink: 0;
&.large {
height: 200px;
}
}
.main {
flex: auto;
display: flex;
flex-direction: column;
overflow-y:auto;
}
.table-wrap {
max-height: 100%;
flex:auto;
}
}
</style>
这种通过html+css的方式也可以做到去除双滚动条的情况。
遗留问题
当.el-table设置了margin-bottom:-2px(只要小于-2px),如果页面高度变小,会导致页面出现间隔50ms的计算,每次的distance是设置的margin-bottom的值。至于具体什么原因造成的,看了源码也没有弄清楚。
大概原因应该是触发了某些隐藏的resize回调函数,导致talbeHeight被重新计算,具体在哪里还没找到。
总结
flex布局看似简单,用起来还是很多弯弯绕。合理利用flex:auto + 100%,可以解决很多结构嵌套导致的内容溢出问题。