沐光

记录在前端之路的点点滴滴

项目布局与 flex

前言

在做项目的整体框架布局时曾用了 3 个版本,由最开始兼容性最好的 position 布局,到中间过渡调整用的 calc 布局,再到目前使用的 flex 布局。因为不考虑兼容性问题,因此用最新的写法来多趟趟水,此篇就是对所写的布局的一些心得。

整体布局

项目的整体外框为品字形布局,顶部为 logo 、主题和用户信息(hover 展示 poper 详情);底部左侧为对应主题的图片,以及其一、二级菜单;底部右侧为内容区。此外最右侧还有一个浮动的快捷栏。

内容区顶部有公告信息(可展示也可隐藏),底部则是自适应内容区。大致结构如下:

项目结构

下面来说说如何来规划布局以及遇到的问题的处理。

历史版本及其问题

position 版本

早期项目并不是很复杂时,使用的是绝对定位的方式。当时顶部无主题、右侧无公告,同时也没有一直悬挂在右侧的浮动框和动画,因此处理比较简单。不过使用 position 来做定位,又一点不是很好的地方是左侧和顶部需要用 margin 撑开(防止内容被挡住),同时超出部分的滚动条展示,以及一部分切换动画的效果呈现最终让我下定决心来更改整体的布局。

calc 版本

中期因为还在开发项目,因此采取了一个过渡方案 calc,将各部分的宽度计算出来,从而做到页面的布局展现。这个版本倒是没什么问题,在 chrome 上运行的很稳定,由于不清楚 calc 计算会不会影响页面的渲染速度,因此最后还是改成了 flex 布局。

flex 布局分析

整体结构

整体的结构可分为上下结构,而下半部分又拆分为左右结构,因此,在设置完顶层的 htmlbody 之后,给项目的最外层 container 元素设以下属性作为外层容器:

1
2
3
4
5
6
7
.container {
height: 100%;
width: 100%;
display: flex;
flex-direction: column;
position: relative;
}

然后顶部设置固定高度 60px,底部默认 flex-grow: 1 撑开即可。然而在内容撑满后,这样设置出现了顶部固定高度部分被压缩了

顶部结构

顶部可分为三个部分:左侧的 logo、中间的主题列表以及右侧的用户头像部分。顶部基本太多问题,因为用户头像部分有多个不同的行内元素,而这块产生了行内元素无法对齐的问题(于是采用了最为方便的 flex 写法)

底部结构

底部内容区域分为左右结构,左侧菜单展示(用 element-menu 很简单),右侧就是内容区了。这部分左侧收缩有做 transition 动画,对于页面图表渲染较多的情况会比较卡(动画去掉了),基本上没遇到什么麻烦;右侧有一个很头疼的问题,那就是页面顶部的公告,因为是可有可无的,而公告下面的内容区要一直保证能够占满内容区(如果内容超出则显示滚动条)。

有人会说:“(折中方案)这有什么难的,内容区 flex 布局,上侧公告区(flex-shrink: 0),下部分内容区默认添上 flex-grow: 1,然后溢出滚动就可以了~”。如果事情能这么简单就好了。需求却并不是上下结构,而是一个整体,因为滚动条区域包裹了顶部公告,同时还要兼容以前的页面,即:

当 “呈现的内容高度” + “公告高度(得考虑无公告的情况)” < “内容区域高度” 时无滚动条;反之有滚动条。此外还需要做好老代码的兼容处理。

emmmmm,果然世界上没有产品所想不出来的主意啊,当然,在“折中方案”上线之后,慢慢的终于摸索出了最佳方案

右侧悬浮框

右侧悬浮框虽然并无太多内容,但是需要在 Vue 项目内支持拖拽,大致为:拖拽时颜色变浅,停止拖拽时恢复原样。由于 Vue 提供 drag 方法,因此比较容易想到的方式是在监听拖拽的起止动作,然后动态绑定 class 控制其 Visibility 显隐性,然而基本上每次开始拖拽后立马就结束拖拽了,最终定位的问题就在 Visibility 上。

遇到的问题

固定高度内容收缩问题

在 flex 布局的模式下,子项的默认值为:

1
flex: 0 1 auto;

其分别表示 flex-grow 为 0,即不会参与多余部分的分配(不可扩张);flex-shrink 为 1,即当整体空间无法装下所有的内容时,该部分会按照一定的规则来压缩;flex-basic 为 auto,即默认以内容区高度为基础高度。

那么问题就来了。因为 flex-basic 为 auto,内容区域高度是由内容来决定的,虽然我们通过设置 height: 60px 将高度固定撑开,但是对于 flex 来说,我的 basic 部分并没有这么高(内容没有达到 60px),同时我这部分又是可压缩的,因此我可以匀一点来尽量使得另一部分能够撑满溢出的内容。而结果可想而知(顶部被压缩了)

解决的思路有三种:

  1. 设置 flex-shrink: 0 来禁止其被压缩
  2. 设置 min-height: 60px 从而保证最低的高度
  3. 将其包裹的内容区高度设置为 60px

行内元素对齐问题

这部分先前有写过浅谈行内元素,因为各部分默认对齐方式不同,此处仅写出两种最为快捷的方法:

  1. 设置父级为 flex,然后设置 align-items: center
  2. 修改各子元素的对齐方式为 vertical-align: center

内容区的智能滚动条问题

问题简单的抽象为一个有固定宽高的 flex 布局的父容器(column 排序),其包含两个子节点:

第一个节点可有可无,呈现时为定高的状态;

第二个节点当内容不是很高时,其会自动填充剩余部分(剩余部分 + 此节点内容 + 第一个节点内容 = 父容器高度);当内容有足够高时,父级会出现滚动条来显示溢出的内容。

老版本此部分的实现是:

1
2
3
4
5
.content {
height: 100%;
width: 100%;
position: relative;
}

理想中最完美的升级方案是在原有的基础上添加一个 flex-grow: 1 然后删除 height: 100%,但是由于要不影响原有的代码(仅使用 flex-grow 不知道为什么,某个页面会出现一个只滚动 1px 距离的滚动条,方案 pass),因此做了下改变即添加一项 min-height: calc(100% - 60px) 然后滚动条就没有了。

前后试了一下,发现必须动态计算的 min-height 值与固定值之和为 100% 时才生效,个人感觉应该是因为刚好占满整个屏幕,因此不会触发多余的 flex 的动态计算,因此也就没有额外的滚动条了;当顶部公告消失时, min-height < height 值,使内容能自动充满屏幕。

如果并非需要兼容历史代码,建议直接 flex-grow: 1,删除 height: 100% 这样应该会好调整一些。

诡异的拖拽事件

最开始写拖拽方法时添加的类为:

1
2
3
.onDrag {
visibility: hidden;
}

在拖拽时隐藏元素,松开时显示元素(最开始实验阶段的 vue/cli3 时可以通过此方法实现),后来版本升级后,该方法失效了,会使得隐藏后会立即事情元素焦点,然后立即出发了 dragend 事件,此较好的解决方法目前是将 visibility: hidden 改成了 opacity: 0,但是更新后拖拽过程中设置元素 visibility: hidden; 会使得焦点丢失,这个原因目前不是很明确。

实例代码

参考例子

参考文章