chunpu / blog

personal blog render by jekyll
MIT License
51 stars 8 forks source link

如果说 Flexbox 之前的布局都是错的... #92

Open chunpu opened 6 years ago

chunpu commented 6 years ago

如果说 Flexbox 之前的布局都是错的...

这张图非常经典

首先解释一下为什么叫交叉轴而不是副轴次轴这样的, 主轴很容易理解, 就是 direction 的方向

交叉轴, 就是和主轴垂直的线, 可以理解为十字交叉口, 如果叫次轴的话并不能明确表达方向的语义

谁说盒子只能竖着排

在 flexbox 出现之前, 盒子默认都是竖着排的, 文字才是横着排的

flexbox 的字面意思是弹性伸缩盒, 但 flexbox 附赠了 direction, 这个缺失已久的属性

这使我们可以轻松写出一个竖着排盒子的容器, 而不再是用各种 trick 技巧

之前的各种布局写法

float

float 设计之初是为了做 图文环绕 这种经典的文章排版的, 没想到被各种 hack 作为 layout 系统

但 float 自己写始终很麻烦, 总是要想着 清除浮动

inline-block

总是要想着解决 inline 本身排列的 3px 间隙问题

table

大部分布局都是简单的一维布局的组合, table 是一个二维布局, 使用场景较为单一, 不过 table 自带了简单的剩余空间填充问题

栅格系统

栅格系统的问题在于, 现实中其实很少有等分布局, 也很少有 offset 偏移布局, 换句话说, 有个设计稿后, 我根本没机会用栅格系统

Flex 的痛点

Flex 的属性太多了, 同时对应的值也很多, 会让人看了经常放弃

有的朋友只用其中2-3个特性, 我见过最多的就是 space-between 和以及默认 flex-direction: column

space-between 过于强大, 彻底解决了元素间隙问题, 而且不再需要单独处理首尾元素, 简直是排版仔的福音

但事实上, 我建议所有布局都用 flex

Flex Container 上有6个属性

Flex Item 上也有6个属性

这么多的属性看的头大, 但我建议只用其中 四个属性, 其他属性如果不是面试的话建议暂时忽略

flex-direction

这个附赠的属性太关键了, 布局本应该可以指定是横向布局还是纵向布局, 从此告别 float 脏实现

justify-content

等同于 text-align

text-align 作用是文字, 而且有继承性, 现在有了 justify-content 只是把之前缺失的功能补上

align-items

vertical-align 非常像, 但 vertical-align 使用场景非常复杂, 概念很容易混淆

flex

这个是 flex 最核心的属性, 设置 flex item 的弹性

你可以把 flex item 想象为蚁人, 可大可小, 可伸可缩

flex: none 的话则没有弹性, flex: 1 则弹性为1

flex 属性是一个简写属性, 就好比 background 属性是一系列属性的组合

[ <‘flex-grow’> <‘flex-shrink’>? || <‘flex-basis’> ]

初始值是 0 1 auto

也就是说默认的 flex item 是自动缩小的布局

问题来了, 什么时候会用 flex-shrink 呢?

答案是几乎没有

因为我们排版时通常是在处理剩余空间的问题, 很少去解决多余空间的问题

"我们要求, 所有布局都是精确的!!!"

"你不会在1000px的页面里面塞4个500px的盒子"

我猜这也是 CSS 工作组推荐大家使用 flex 组合属性的原因 (因为你用不到 flex-shrink, 逃..

虽然 flex-basis 的默认值是 auto, 但 flex: number 会把 flex-basis 设为 0%

很简单, 当我们算权重的时候, 不会去考虑 item 本身的长度, 我们需要一起回到起跑线, 从头再来

因此, 我们不用 flex-shrink, 也会把 flex-basis 设为 0, 以及只会使用 flex-grow

那正好, 官方提供的 flex: number 简写就是此功能, 完美

事实上 css 工作组直接在文档里说了鼓励使用 flex 这个简写, 因为标准已经帮我们适配好了常用场景

另外所有多行属性都不建议使用, 比如 flex-wrap, flex-flow, align-content

完全可以使用单行 flexbox 组合的方式来实现, 这样理解起来更容易

order 自定义顺序非常反人类, 会造成歧义, 也不建议使用

假设之前的布局都是错的

由于时代的局限性, 假设任何之前的 layout 写法都是错的, 我们要推翻桌子重来

我们不关心标签的语义性, 忘掉之前 display inline 和 block 的设定, 忘掉 BFC, IFC!

我们把所有容器都写成 column 和 row 的组合, 因为一个容器, 我们只关心它内部是竖排还是横排

封装 css 工具类

封装我们的 flex 布局工具类

先封装三大基本类, 解决横排和竖排和剩余空间的基本问题

.flex-col {
  display: flex;
  flex-direction: column
}

.flex-row {
  display: flex;
  flex-direction: row
}

.flex-1 {
  flex: 1
}

其次封装5种主轴的分布方式

.main-align-start {
  justify-content: flex-start
}

.main-align-end {
  justify-content: flex-end
}

.main-align-center {
  justify-content: center
}

.main-align-space-between {
  justify-content: space-between
}

.main-align-space-around {
  justify-content: space-around
}

封装6种交叉轴的分布方式(多了一个继承)

.cross-align-start {
  align-items: flex-start
}

.cross-align-end {
  align-items: flex-end
}

.cross-align-center {
  align-items: center
}

.cross-align-baseline {
  align-items: baseline
}

.cross-align-stretch {
  align-items: stretch
}

.cross-align-inherit {
  align-items: inherit;
}

复合属性

.main-cross-center {
  justify-content: center;
  align-items: center
}

如果你用的也是 Vue 的话, 可以封装两个基本的容器

横排容器 row.vue

<template>
  <div class="flex-row">
    <slot></slot>
  </div>
</template>

竖排容器 column.vue

<template>
  <div class="flex-col">
    <slot></slot>
  </div>
</template>

main.js 加入这两个属性

import row from '@/components/row'
import column from '@/components/column'

Vue.component('row', row)
Vue.component('column', column)

尝试实现一个最经典的带 sidebar 的页面布局

image

<row id="app">
  <column class="sidebar main-cross-center">
    sidebar
  </column>
  <column class="flex-1">
    <row class="header main-cross-center">
      header
    </row>
    <column class="main flex-1 main-cross-center">
      main
    </column>
    <row class="footer main-cross-center">
      footer
    </row>
  </column>
</row>

几乎不需要写任何布局相关的代码, 写完后只需要补充一些 margin, 字体相关的属性即可

兼容性问题

IE10不支持 flex-wrap, flex-flow, align-content

不过没关系, 如果我们坚持使用单行 flex 布局, 我们永远都用不到这些属性

事实上, 我认为如果你要使用二维布局, 应该使用多个 flex 布局嵌套组合的方式, 这样容易理解多了

因此我们认为 IE10+ 都支持 flex 布局

直到有同事分享之后才发现 Flex 也是可以 polyfill 的...可以兼容到IE8, IE9~

具体参见此项目, 截至目前已经4000+ star

https://github.com/jonathantneal/flexibility

我们可不可以直接像 border-box 那样直接写 * {display: flex} 呢?

不行的, 因为 flex 毕竟是容器布局, 不应该给所有元素加上

只支持 Flexbox 的快应用

最近在学习快应用, 突然发现快应用的样式只支持 Flexbox, 不支持 block, inline 这些属性值, 可以说是英雄所见略同了

也就是说, 怎么排盒子, 竖着排还是横着排, 应该由容器来决定, 而不是元素自己来决定

忘掉 inline, block 这些历史局限, 全面拥抱 flex 才是正解

Flexbox 的定义解决明了, 对开发者和使用者都是好事, 是一个 win-win 的 happy ending, 甚至在 Android, Canvas 等平台里面都可以查到 Flex Layout 相关的开源实现

chunpu commented 6 years ago

经典布局完整实现 App.vue

<template>
  <row id="app">
    <column class="sidebar main-cross-center">
      sidebar
    </column>
    <column class="flex-1">
      <row class="header main-cross-center">
        header
      </row>
      <column class="main flex-1 main-cross-center">
        main
      </column>
      <row class="footer main-cross-center">
        footer
      </row>
    </column>
  </row>
</template>

<script>
export default {
  name: 'app'
}
</script>

<style scoped>
.sidebar {
  width: 210px;
  background: #d3dce6;
}
.main {
  /* 滚动容器 */
  overflow: auto;
  background: #e9eef3;
}
.header {
  background: #b3c0d1;
  height: 60px;
}
.footer {
  background: #b3c0d1;
  height: 60px;
}
</style>

<style>
@import 'flex.css';

* {
  margin: 0;
  padding: 0;
}

html, body, #app {
  height: 100%;
  font-size: 24px;
  color: #333;
}
</style>
chunpu commented 6 years ago
/* flex 工具类 */
.flex-col {
  display: flex;
  flex-direction: column
}

.flex-row {
  display: flex;
  flex-direction: row
}

.flex-1 {
  flex: 1
}

.main-align-start {
  justify-content: flex-start
}

.main-align-end {
  justify-content: flex-end
}

.main-align-center {
  justify-content: center
}

.main-align-space-between {
  justify-content: space-between
}

.main-align-space-around {
  justify-content: space-around
}

.main-cross-center {
  justify-content: center;
  align-items: center
}

.cross-align-start {
  align-items: flex-start
}

.cross-align-end {
  align-items: flex-end
}

.cross-align-center {
  align-items: center
}

.cross-align-baseline {
  align-items: baseline
}

.cross-align-stretch {
  align-items: stretch
}

.cross-align-inherit {
  align-items: inherit
}