惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 三生石上(FineUI控件)
V
Vulnerabilities – Threatpost
C
Cisco Blogs
A
Arctic Wolf
L
LINUX DO - 热门话题
P
Proofpoint News Feed
Security Latest
Security Latest
AWS News Blog
AWS News Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Application and Cybersecurity Blog
Application and Cybersecurity Blog
Cisco Talos Blog
Cisco Talos Blog
L
Lohrmann on Cybersecurity
W
WeLiveSecurity
爱范儿
爱范儿
Last Week in AI
Last Week in AI
Hacker News - Newest:
Hacker News - Newest: "LLM"
S
Security Affairs
PCI Perspectives
PCI Perspectives
C
Cybersecurity and Infrastructure Security Agency CISA
Spread Privacy
Spread Privacy
IT之家
IT之家
月光博客
月光博客
云风的 BLOG
云风的 BLOG
宝玉的分享
宝玉的分享
J
Java Code Geeks
美团技术团队
酷 壳 – CoolShell
酷 壳 – CoolShell
I
Intezer
博客园_首页
博客园 - 司徒正美
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
P
Palo Alto Networks Blog
NISL@THU
NISL@THU
Recent Commits to openclaw:main
Recent Commits to openclaw:main
有赞技术团队
有赞技术团队
K
KPMG report finds enterprise disconnect between AI and its ROI | CIO
量子位
The Last Watchdog
The Last Watchdog
Google Online Security Blog
Google Online Security Blog
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
博客园 - 聂微东
N
News and Events Feed by Topic
Webroot Blog
Webroot Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
S
Security @ Cisco Blogs
罗磊的独立博客
大猫的无限游戏
大猫的无限游戏
The Cloudflare Blog
V
V2EX
Jina AI
Jina AI

愆伏

vue feedback 指令开发 通过webpack对css压缩后所引起的重名问题 NPM 撤回已发布的版本 昆虫有趋光性吗 数组转成字符串(css 属性) 阿里云和腾讯云网站字体族对比 Vue组件抛事件和Function prop的差别 Vue 事件参数传递 Vue Data里面下划线命名无效并报错 使用sublime中的正则处理数据格式 通过nodejs创建目录与文件 macOS 下npm run dev 无法局域网访问 mac 下使用ssh访问非22端口的gitlab 世界是平的,在科学上网之后 使用sublime text3和Markdown来写博客 如果非要给一个标题,那就是2014年非工作总结 不专业呢,就别捣乱 对知乎大号怒撤事件的一点看法 拒绝SB思维 嘀嘀与快的的商业博弈 关于收藏夹功能的思考 你居然都不用微信 一个英语培训机构的外呼电话 智商是硬伤就无解了 扁鹊三兄弟 竖子不足与谋 美丽说、蘑菇街今天一定开了好几个小时的会 个人独立博客会死掉吗? iPad作画 google reader即将关闭,谁来候补? 去年 美国人又火星了 一个简单的flash射击游戏 Draw an angry bird with pure CSS 用css3画一坨屎 一只音箱——漫步者M0pro 瓢虫图标 如果能够收集到世界上所有人的梦 作为一名产品经理 wordpress换域名 程序员和站长之间资源的交换 独角仙饲育日记1 男人是得有点装备的 镇江火车站、新汽车站到香逸渔港大酒店路线 jquery的slideUp、slideDown在IE中的闪动bug 《玩赚你的网站》是本难得的好书 腾讯微博邀请,欢迎自取 福特2010最新款家用普及型车 关于小霸王是否可以玩魔兽世界的问题 君要臣死,臣不得不死 wordpress主题发布:iCake 1.0 脆弱的域名服务 舍身成仁,舍己为人 欲练神功,挥刀自宫——在没有google之后 杯具掉的手作软陶皮卡丘 凶猛的阿凡达 js+css控制字体变换 2009年下半年南艺自考设计素描(本科) 愤怒摔食图——去TMD的xxx 改裤衩 紫金山捉虫记 态度问题还是能力问题 慷慨的饭店老板 南艺自考顺利达标 2009上半年南艺艺术设计自考毕业设计 说说产品经理-前言 在线生成卡通头像 CG weaver 1~3期打包下载 育儿网招聘网页设计师 google calendar的另类用法 互联网产品、运营二三事 小情调 干切牛肉雪菜面加煎蛋x2 永远的龙珠 M8:我不是山寨 终于回来了 还不都为了大客户 古典音乐——冷笑话 豆瓣盈利能力堪忧? 骑马与砍杀(mount&blade)——另类的爽快 溺爱的代价 开心不开心? 超赞的视频广告——wario land shake 神奇的《孢子spore》 该相信用户的话吗? chrome——google浏览器真的来了! 浏览器:web世界之门 赤壁——三国梦 项目管理就是博弈 Pia ji 一声的快感 Memories 拿来主义 一声叹息 禅心 LBS^2转到wordpress2.5 尽我们所能帮助灾区,希望灾民平安度过难关 游高崇寺 Avant Browser的复兴 “知”的提升 尼斯湖怪-冷笑话
关于grid布局的一些思考
tortorse · 2018-04-11 · via 愆伏

0. 基础概念

0.1. 什么场景

当一个设计者在进行排版初期时,想要对布局进行整体划分

0.2. 什么问题

只能局部设计,无法从宏观角度设计并逐步深入细节。

0.3. 解决方案

通过对布局进行简单的「数据结构」描述,比如「一棵树」描述,生成可视化的布局结果

0.4. 概念阐述

在容器上进行布局轨道的划分,当内容填充进去时会按照轨道排列

具体解释: Basic concepts of grid layout

1. 数据结构

从具象的角度来出发,一般我们的划分一个区域的过程是这样的:

  1. 先画一个「矩形」
  2. 将这个「矩形」进行分割,从而形成新的矩形区域

做一次最简单的划分:

  1. 先画一个「矩形」1
  2. 将这个「矩形」分为2个,形成了1.11.2
  3. 再将1.2分为2个,形成了1.2.11.2.2

那么可以表达为以下结构

1
2
3
4
5
1
|- 1.1
|- 1.2
|- 1.2.1
|- 1.2.2

使用对象和数组进行表达

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{
name: '1',
children: [
{
name: '1.1'
},
{
name: '1.2',
children: [
{
name: '1.2.1'
},
{
name: '1.2.2'
}
]
}
]
}

2. 嵌套关系

采用类似html的方式来进行表达

1
2
3
4
5
6
7
<area name="1">
<area name="1.1"></area>
<area name="1.2">
<area name="1.2.1"></area>
<area name="1.2.2"></area>
</area>
</area>

3. 结构演化

仅凭最基础的数据结构,是无法进行视觉表达的,因为不知道这块区域有多大,也不知道它在哪儿。在视觉上,我们认为的「区域」是有「面积」概念。在这里我们通过「宽度」和「高度」对「区域」进行描述

假设我们做这样一个布局

area

3.1 宽高结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
{
name: '1',
width: 1024,
height: 1024,
children: [
{
name: '1.1',
width: 512,
height: 1024
},
{
name: '1.2',
width: 512,
height: 1024,
children: [
{
name: '1.2.1',
width: 512,
height: 512
},
{
name: '1.2.2',
width: 512,
height: 512
}
]
}
]
}

按照以上结构,虽然对「面积」有了描述,但是对于「位置」并没有阐释。那我们再引入一个包含xy两个维度的坐标系。

我们用的「坐标」来描述一个「矩形」,我们需要四个

前提是矩形的相邻边都是互相垂直的

在一个平面直角坐标系中,一个的描述为

用4个点来表达一个矩形

我们可以认为矩形是4条线段组成的图形

  • 第一条线段:
  • 第二条线段:
  • 第三条线段:
  • 第四条线段:

3.2 坐标结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
{
name: '1',
coords: [
{
x: 0,
y: 0
},
{
x: 1024,
y: 0
},
{
x: 0,
y: -1024
},
{
x: 1024,
y: -1024
}
],
children: [
{
name: '1.1',
coords: [
{
x: 0,
y: 0
},
{
x: 512,
y: 0
},
{
x: 0,
y: -1024
},
{
x: 512,
y: -1024
}
]
},
{
name: '1.2',
coords: [
{
x: 512,
y: 0
},
{
x: 1024,
y: 0
},
{
x: 512,
y: -1024
},
{
x: 1024,
y: -1024
}
]
children: [
{
name: '1.2.1',
coords: [
{
x: 512,
y: 0
},
{
x: 1024,
y: 0
},
{
x: 512,
y: -512
},
{
x: 1024,
y: -512
}
]
},
{
name: '1.2.2',
coords: [
{
x: 512,
y: -512
},
{
x: 1024,
y: -512
},
{
x: 512,
y: -1024
},
{
x: 1024,
y: -1024
}
]
}
]
}
]
}

全部用点来表达显得非常复杂,考虑在web前端的场景中,我们来试图简化一下

既然是矩形,其表达可以如下

在浏览器的区域里,我们可以认定原点是在左上角。

  • 原点右侧x+
  • 原点左侧x-
  • 原点上方y-
  • 原点下方y+

原点坐标是(0,0),并且位于左上方

那么我们可以只通过4个参数值来表达一个区域:

  • x 左上原点x坐标
  • y 左上原点y坐标
  • width 矩形宽度,实际是右下点的x坐标
  • height 矩形高度,实际是右下点的y坐标

3.3 宽高和坐标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
{
name: '1',
x: 0,
y: 0,
width: 1024,
height: 1024
children: [
{
name: '1.1',
x: 0,
y: 0,
width: 512,
height: 1024
},
{
name: '1.2',
x: 512,
y: 0,
width: 512,
height: 1024
children: [
{
name: '1.2.1',
x: 512,
y: 0,
width: 512,
height: 512
},
{
name: '1.2.2',
x: 512,
y: 512,
width: 512,
height: 512
}
]
}
]
}

到这一步,我们发现这其实就是「绝对定位」,那么其缺点也显而易见了。就是每一块区域都是独立的,相互之间的关系需要你严格设定。

大多数的设计师在布局的时候都是先画一个区域,然后再画一个区域,逐个进行调整。然后带来的问题就是,设计师最讨厌用设计工具做「表格」类的东西。

为什么会这样?

因为设计工具提供的布局方式就是「绝对定位」

根据以上所示,绝对定位里的概念总结起来就只有一个,叫做「坐标」

3.4 方向、继承以及分配

从上面的结构,我们可以发现几个事情

  • 1的原点和1.1的原点是同一个
  • 1.2的宽度等于1的宽度减去1.1的宽度
  • 1.2.11.2.2的宽度等于1.2的宽度

我们要完成的是「布局」而非「绘图」,所以我们可以让这件事情变得更加简单一些。我们分步来说明

3.4.1 划定基础区域

我们首先来制作一块区域,它的名字叫1,并且宽度是1024、高度也是1024,原点位置为(0,0)(0,0)我们把它设为缺省值,所以不表达。

1
2
3
4
5
{
name: 1,
width: 1024,
height: 1024
}

3.4.2 引入分割方向概念

我们现在对其分割,那么这个时候就产生了一个问题,我们是按「水平」方向分割,还是按「垂直」方向分割?因此,我们引入方向(维度)概念。我们简单的定义一下

  • column ,即在「水平」方向上进行分割
  • row , 即在「垂直」方向上进行分割

那么我们对1进行水平方向上的分割

1
2
3
4
5
6
{
name: '1',
width: 1024,
height: 1024,
split: 'column'
}

3.4.3 引入份数概念

那么分割为几份呢?这时候我们又要引入一个份数的概念,那么我们现在就分成2份。

1
2
3
4
5
6
7
{
name: '1',
width: 1024,
height: 1024,
split: 'column',
part: 2
}

3.4.4 在另一方向上的继承

这个时候我们意识到,因为是按「水平」方向分割,那么每一块的高度应该是和原来的区域是一样的,相当于每一块都继承了原有区域的高度,但是宽度因为被分割了,所以没法继承。

我们得考虑宽度的分配,现在我们将宽度进行了分割,那么每一块占原来宽度的多少呢?我们假设是均分,每一块就是50%,那么用一个值去表达

1
2
3
4
5
6
7
8
{
name: '1',
width: 1024,
height: 1024,
split: 'column',
part: 2,
distribute: 0.5
}

上面的结构可以表述为

有一块1024 * 1024 的区域,拆分成2列,每一列的宽度是1024 * 0.5

2column

同理如果是按「垂直」方向分割的话,则高度需要被分割,而宽度可以继承

3.4.5 引入等份单位

那如果不等分怎么办?我们的一个distribute不足矣表达,那么如果distribute是一个数组的话,数组的length其实就代表了part的数量,而且可以直接用对象的key来标识方向,所以可以形式上简化一下

1
2
3
4
5
6
{
name: '1',
width: 1024,
height: 1024,
columns: [0.4, 0.6]
}

但是以上结构会遇到所有的distribute之和不是1,既可能出现大于1,也可能出现小于1的情况。这时候我们引入一个「等份」的概念,叫做fr1fr代表空间的1等份

比如说,我们把空间分成3块,第一块占3fr,第二块占2fr

那么这两块区域的实际宽度是

  • 第一区域宽度:
  • 第二区域宽度:

这下原来的数据结构可以表达为

1
2
3
4
5
6
{
name: '1',
width: 1024,
height: 1024,
columns: [1fr, 1fr]
}

3.4.6 绝对数值

并不是所有的情况都是按等份来分配的。比如某块区域,我们想让它占据一个绝对数值的宽度。

1
2
3
4
5
6
{
name: '1',
width: 1024,
height: 1024,
columns: [200, 1fr, 2fr]
}

这个时候的计算其实是,把200宽度的区域从原来的里面扣除,在进行等份的计算

两块区域的实际宽度是

  • 第一区域宽度:
  • 第二区域宽度:
  • 第三区域宽度:

那如果全都是绝对数值会怎样

比如有一个区域宽是800,我们分3列,每一列宽度是200

1
2
3
4
5
6
{
name: '1',
width: 800,
height: 800,
columns: [200, 200, 200],
}

这个时候其实就是分开处理了,1本身是一个「区域」,3列构成的是另一个「区域」

如果分割的区域之和超出了被分割的区域又该怎么样呢?

其实和上面是一样的,实际上原有的「区域」和实际的「被分割区域」是分开处理的

总结一下就是

当「区域分割规则」都是「绝对数值」的时候,那就直接「划定区域」,而不去做「分割」

前端小伙伴这个时候都笑了,写了这么废话不就是CSS的grid么

我们把最初的数据结构来一次梳理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
{
name: '1',
width: 1024,
height: 1024,
columns: [1fr, 1fr],
children: [
{
name: '1.1'
},
{
name: '1.2',
rows: [1fr, 1fr]
children: [
{
name: '1.2.1'
},
{
name: '1.2.2'
}
]
}
]
}

是不是很简单,但是上面其他的所有部分是不是很复杂?

这就是为什么我们平时用别人做好的东西很简单,但是如果自己实现却无从下手的原因。

3.4.7 回归到坐标系

以上的结构我们再还原到坐标系画线的模式中,看看是怎样的

先画一个最大的「区域」1

1
2
3
4
lineTop: (0,0)(width,0) 
lineRight: (width,0)(width,height)
lineBottom: (width,height)(0,height)
lineLeft: (0,height)(0,0)

1.1

1
2
3
4
lineTop: (0,0)(width*1/(1+1),0) 
lineRight: (width*1/(1+1),0)(width*1/(1+1),height)
lineBottom: (width*1/(1+1),height)(0,height)
lineLeft: (0,height)(0,0)

1.2

1
2
3
4
lineTop: (width*1/(1+1),0)(width,0) 
lineRight: (width,0)(width,height)
lineBottom: (width,height)(width*1/(1+1),height)
lineLeft: (width*1/(1+1),height)(width*1/(1+1),0)

1.2.1

1
2
3
4
lineTop: (width*1/(1+1),0)(width,0) 
lineRight: (width,0)(width,height*1/(1+1))
lineBottom: (width,height*1/(1+1))(width*1/(1+1),height*1/(1+1))
lineLeft: (width*1/(1+1),height*1/(1+1))(width*1/(1+1),0)

1.2.2

1
2
3
4
lineTop: (width*1/(1+1),height*1/(1+1))(width,height*1/(1+1)) 
lineRight: (width,height*1/(1+1))(width,height)
lineBottom: (width,height)(width*1/(1+1),height)
lineLeft: (width*1/(1+1),height)(width*1/(1+1),0)

4. 程序实现

我们发现「布局」的根本方式是「绝对坐标」,在之上抽象出了「栅格(grid)」。非常幸运的是,CSS已经提供了这层抽象。

如果做成vue组件的话,形式化方面不想采用近似iview<row> </col>嵌套结构,而是只有一个area组件,通过一个数据结构来完成area的递归

4.1 组件属性

基于以上的观察,我们可以构建一个组件叫做area,那么它应该包含以下属性

  • width 区域的宽度可选
  • height 区域的高度 可选
  • columns 区域按列切分
  • rows 区域按行切分
  • name 区域的名称

但是我们之前说过,在另一个方向上存在继承关系,那么应该避免同时设置columnsrows,另外考虑到宽度和高度可以从上级继承,我们做一次改进

  • width 区域的宽度可选
  • height 区域的高度 可选
  • grid: 可选columns rows
  • distribute: 分配规则
  • name 区域的名称

4.2 递归

按grid进行递归,如果发现没有grid,就停止递归

5. 用户体验

5.1 兼容性

不是所有的浏览器都支持grid,保险的做法是使用百分比

但是我们就不选择兼容方案了,激进一点

5.2 显性表达区域

用边框表达,还是用颜色表达

  • 用边框会占用区域
  • 颜色要求相邻的区域颜色不重复

5.3 模板

  • 左右
  • 上下
  • 圣杯