我是一名后端程序员,我学不会 css。花40美元买本 https://every-layout.dev/ 之后,我想了一种写法来表达前端的布局排版。前端大神们可以退散了
布局原语
vant 里有一个 vant-cell 的组件
这样的布局组件很有用,但是用途非常单一。只能用于左边显示一个label,右边显示一个value,中间留白的情况。用这样的布局组件会导致开发者需要记忆大量的布局组件的名字和布局排版效果的对应关系。我希望像我这样的 css 无能者也能够通过记忆一些简单的布局组件来实现”布局不求人“的效果。虽然这样排版写得很机械,一点也不优雅,不语义化,但是 just works。
布局组件包括(均未实现,仅靠想象):
- Flex Formatting Context
- Row:x轴
- Col:y轴
- Box:参与 x,y布局时候的宽高占位
- Block Formatting Context
- Overlay:z轴
- Inline Formatting Context
- RichText/Span:流式文本布局
- media query:不用组件实现,用 javascript 写,走 react 重渲染的模式
下面是对 every-layout 中的布局的枚举
- Row 布局
- list of links:Row 的 gap 属性,overflow="wrap"
- dock to left & right:Row & spacer Box
- row center:Row & 双 spacer Box
- media object:minWidth Box
- sidebar media object:Row折行控制一
- product list:Row 折行控制二
- product grid:Row 折行控制三
- inline form:同 sidebar
- input group:Row 的 overflow="shrink"
- reel images:Row 的 overflow="scroll"
- label with a visual cue:Row 的 inline 属性
- Box 包装
- padding & border:Box 突出内容
- auto width:Box维持图片本身的宽高比
- aspect ratio:Box按指定宽高比裁切
- Column 布局
- stacked inputs:Col 的 gap 属性
- splitting the stack:Col & spacer Box
- column center:Col & 双 spacer Box
- Overlay 布局
- viewport modal:无 confined
- obscured content:confined="both"
list of links
理想的写法
<
Row 默认的行为就是宽度不够了之后自动换行。默认有一个 1em 的 gap。如果要调整 gap:
<
Row 有默认的 gap,就不需要在每个子元素上设置 margin 之类的东西来保持两个子元素之间的间距了。
dock to left & right
这个在上面的需求的基础上添加了靠左和靠右的需求。"list of linked keywords" 里的每个link都是往左靠齐的。而这个例子里需要两个方向的靠齐。
理想写法
<
想法是 img 和 ul 是被“挤”到左右两边的。中间的 Box 会把所有剩余的横向空间都给“吃掉”。这里用 spacer 为 true 来表示这个 Box 在当前的布局方向上起到弹簧占位的效果。
row center
水平居中
<
左右两个 spacer,把剩余的空间都消耗掉,使得button可以水平居中显式。
media object
理想的写法
<
Box 除了可以用 spacer 属性占位,可以显式设置 minWidth 的方式来进行占位。Box 的主要作用就是调整自己所包含的内容如何参与 Row/Col 的排版,进行宽或者高的占位。如果没有 Box 包裹,则是内容用自己本身的内容宽高直接参与排版。
sidebar media object
在 media object 的基础上,额外的需求是实现 sidebar 效果。也就是在折行发生之后,对折行的行为进行控制。如果不加 sidebar 效果,上面的例子里的图片是不会进行100%宽度的拉伸的,而是保持图片本身的自有宽度。
理想写法
<
这里添加了一个新的Box属性,用来实现 sidebar 风格的占位。这样当一行放得下的时候,左边的 Box 是 sidebar,右边的 Box 是 spacer,那么 spacer 就会占据 100% 的剩余空间。当一行放不下的时候,两个Box都是独占一行的,sidebar 这个时候就表现出和 spacer 一样的行为,会占据 100% 的剩余空间。
product list
这个布局是为了避免下面的中间状态,因为这个中间状态会让折行的那个商品显得“过于突出”。
这个和 sidebar 一样,都是为了控制折行发生之后的行为。
理想写法:
<
通过设置 childMinWidth 表达了三个含义
- 所有的 child 都是等宽的,因为只有一个宽度设置
- child 宽度如果小于 30em,则要发生折行
- 折行之后不需要出中间状态,直接切换成一列显式
product grid
product grid 和 product list 的区别是多了中间的 grid 的状态。product list 始终是成一行或者一列排列的,product grid 则会折成多行展示。
理想的写法
<
和 product list 的写法是类似的,多了一个 grid 为 true 的属性。表示接受 grid 这个中间的折行状态。
总结一下,三种折行之后的行为
- sidebar media object:折行之后,sidebar变成占满全屏的宽度
- product list:折行之后直接变成一列展示
- product grid:折行之后比 product list 多一个 grid 的中间状态
inline form
这个例子和 sidebar media object 是一样的,复习一遍
理想的写法
<
input group
如上图所示的 input group 粗看和 inline form 是一样的。但是 input group 不应该发生换行。这就需要我们给 Row 添加一个属性来控制是否折行
理想的写法
<
默认的 overflow 行为是 wrap。如果指定为 shrink 则会对每个子元素均等比例 shrink 以适应宽度。
sausage links
这个显然也是由 overflow 引起的,宽度不足的行为需要被控制。
理想的写法
<
和 shrink 不同,hidden 就是直接“切掉”显式不下的内容。
reel images
理想的写法
<
宽度来自于 img 自身的内容宽度。累积宽度超过了之后触发滚动条。
label with a visual cue
理想的写法
<
通过 inline 属性,把两条 inline 的内容合并成一行显式,在两条内容之间添加默认的 gap,并用 baseline 进行对齐。
padding & border
Box 除了参与宽高占位之外,还可以让内容显得更加突出。包括添加 padding 和 border 两种方式。上面的布局的理想写法
<Box border>
<Box invert padding="1em">head</Box>
<Box padding="1em">body</Box>
</Box>
Box 默认没有 padding 也没有 border,需要显式指定。border 如果设置为 true,则使用全局设置的细边框宽度。
auto width
当高度固定的时候,用高度来反推宽度
高度变化了,对应的宽度也变化了。
理想的写法
<
我们用外包 Box 的 width/height 方式来 override 内容本身的高度和宽度。也就是 top => bottom 的尺寸确定过程。如果不指定 width/height spacer/sidebar 这些,Box 的尺寸是 bottom => top 的方式来确定的,由内容来决定。
aspect ratio
无论图片是什么样的宽高比,都按照 Box 指定的宽高比进行裁切
理想写法
<
宽度来自于内容,或者来自于父容器的指定。但是高度始终是宽度按照aspectRatio折算而来。
Box 本质上仍然是一个可被布局的原子 content,参与到 Row/Column 布局里。
- 指定了一份 content 如何参与 Row/Column 的布局算法,通过 minWidth/minHeight/spacer/sidebar 等属性控制
- 对 content 进行二次加工,成为一份新的 content,添加 padding/border,裁切边框等
stacked inputs
这个表单有两层 stack。字段与字段之间用大间距 stack,表达这是两个独立的字段。字段内的 label/input/error message 用小间距的 stack,表达这些元素是内聚的。
理想的写法
<
splitting the stack
左侧的 slide 框和 Add slide 按钮中间需要一个留白
理想的写法
<
column center
Col 不像 Row,Row 的 overflow 默认是 wrap,Col 的 overflow 行为就是 hidden。
当高度不够的时候,footer 仍然是被撑到底部的,这个也被称作 sticky footer。
理想的写法
<
viewport modal
以 viewport 来计算位置,并不限制高和宽
理想的写法
<
obscured content
以父元素的位置来计算 overlay 的位置,并且限制高和宽
<
confined="both" 表示高和宽,都不能超过父一级 overlay 的边界。如果没有 confined 则 overlay base 仅仅只是改变了上一层 overlay 的 xy 坐标位置,并不控制边界。
base 表示这个是最底层的 overlay 了,不要从文档流里移除。base 是用来 confine 其他 overlay 用的。如果没有这一层 base,默认的“base”就是 viewport 本身。
that is all
大概的需求就想到这些,想到了再补