el-table高度自适应引发的flex布局思考

8,184 阅读6分钟

起因

flex布局想必大家都已经用了很久了,element的el-table大家也应该用过,最近公司的一个项目中有一个基于flex布局 + el-table的高度自适应的问题让我又重新学习了一遍flex。

问题描述

页面的整体布局大概如下

image.png 其中页面头部和导航区域是固定的,然后整体页面是通过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>

image.png 可以看到,child2占据了剩余的300px的像素大小。为什么flex:auto就可以做到呢。其实是因为flex是一下三种属性的缩写

  1. flex-grow:代表子元素基于剩余空间的放大比例,默认是0,表示即使有剩余空间也不放大。
  2. flex-shrink:表示子元素在空间不足时的缩小比例,默认是1,表示如果空间不足,子元素会按比例缩小。如果为0表示关闭。
  3. flex-basis:表示子元素在分配多余空间之前所占据主轴/交叉轴空间的大小,默认值是auto,表示项目原本的大小。 flex取值有三种:
  4. flex:auto => flex: 1 1 auto;
  5. flex:1 => flex: 1 1 0%;
  6. flex:none => flex: 0 0 auto; 上面我们定义了flex:auto,即定义了flex-grow为1,所以child2默认占据了剩余的300px像素。

那么如果child2定义了像素,且像素大于300px像素(比剩容器余像素大),会出现什么情况呢? 如果我们给child2做如下样式改变

 .child2 {
    width:800px;
    background: yellow;
}

image.png 具体算法如下:
容器总体宽度为500px,实际所有子元素宽度总和为1000px(100+800+100),child1和child3占据100/1000*500 => 50px,child2占据800/1000*500 => 400px
此时如果child1和child3设置了flex-shrink:0,会出现上面情况呢?

image.png 由于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>

image.png 如图所示,content的宽度撑开了right,导致right的宽度大于了container的宽度500,所以right侵占了left的空间。此时需要给left指定flex-shrink:0。

image.png 此时left的宽度没有被占据,right的宽度依然被撑开。在实际开发过程中,我们肯定不想出现这种情况。
下面我们给right设置一个overflow-x:auto,给溢出部分设置滚动条。

.right {
    flex:auto;
    display: flex;
    overflow:auto;
}

image.png 这时你会惊奇的发现,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>

image.png 宽度虽然没有被撑开,但是超出部分会导致大量留白。此时我们还需要给content设置overflow-x:auto。

image.png 你以为这就结束了?那你又年轻了。看一个更加接近实际的例子。

<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>

image.png 惊不惊喜,意不意外。当存在兄弟元素的时候,我胡汉三又回来了。
彻底解决这个顽疾需要在content外部再套一层div。

image.png

image.png 以上这种操作我称之为100% + auto解决方案,即外部定义宽度/高度,然后内部元素继续使用auto。这样就不会导致因为auto造成的溢出问题。
反之,如果外部定义flex:auto,内部元素使用100%,也是一样的效果。

image.png

el-table

image.png element的el-table,他的height属性可以给表格设置高度,当表格区域内容高度大于设置的height时,就会出现表格滚动条。如果将height设置为100%,那么他就会默认填充父元素的高度,当浏览器/父元素的高度变小时,会压缩.el-table的高度,导致高度溢出,从而出现双滚动条。

image.png 知乎上有一篇文章,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>

上面例子中,当我们页面头部变大时,会导致出现双滚动条的情况 image.png 下面我们修改一下页面结构

<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>

image.png 这种通过html+css的方式也可以做到去除双滚动条的情况。

遗留问题

当.el-table设置了margin-bottom:-2px(只要小于-2px),如果页面高度变小,会导致页面出现间隔50ms的计算,每次的distance是设置的margin-bottom的值。至于具体什么原因造成的,看了源码也没有弄清楚。

image.png

image.png

image.png 大概原因应该是触发了某些隐藏的resize回调函数,导致talbeHeight被重新计算,具体在哪里还没找到。

总结

flex布局看似简单,用起来还是很多弯弯绕。合理利用flex:auto + 100%,可以解决很多结构嵌套导致的内容溢出问题。