css
里面有不少简写的属性,比如
padding: 10px 2px 12px 4px
那么如果是通过js
的数组来表达
[10, 2, 12, 4]
从js
转成css
Array.from([10,2,12,4],x => x + 'px').toString().replace(/,/g, ' ')
// 10px 2px 12px 4px
css
里面有不少简写的属性,比如
padding: 10px 2px 12px 4px
那么如果是通过js
的数组来表达
[10, 2, 12, 4]
从js
转成css
Array.from([10,2,12,4],x => x + 'px').toString().replace(/,/g, ' ')
// 10px 2px 12px 4px
当一个设计者在进行排版初期时,想要对布局进行整体划分
只能局部设计,无法从宏观角度设计并逐步深入细节。
通过对布局进行简单的「数据结构」描述,比如「一棵树」描述,生成可视化的布局结果
在容器上进行布局轨道的划分,当内容填充进去时会按照轨道排列
具体解释: Basic concepts of grid layout
从具象的角度来出发,一般我们的划分一个区域的过程是这样的:
做一次最简单的划分:
1
1.1
和1.2
1.2
分为2个,形成了1.2.1
和1.2.2
那么可以表达为以下结构
1
|- 1.1
|- 1.2
|- 1.2.1
|- 1.2.2
使用对象和数组进行表达
{
name: '1',
children: [
{
name: '1.1'
},
{
name: '1.2',
children: [
{
name: '1.2.1'
},
{
name: '1.2.2'
}
]
}
]
}
采用类似html的方式来进行表达
<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>
仅凭最基础的数据结构,是无法进行视觉表达的,因为不知道这块区域有多大,也不知道它在哪儿。在视觉上,我们认为的「区域」是有「面积」概念。在这里我们通过「宽度」和「高度」对「区域」进行描述
假设我们做这样一个布局
{
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
}
]
}
]
}
按照以上结构,虽然对「面积」有了描述,但是对于「位置」并没有阐释。那我们再引入一个包含x
,y
两个维度的坐标系。
我们用点
的「坐标」来描述一个「矩形」,我们需要四个点
前提是矩形的相邻边都是互相垂直的
在一个平面直角坐标系中,一个点
的描述为
用4个点来表达一个矩形
我们可以认为矩形是4
条线段组成的图形
{
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坐标
即左上原点y坐标
即矩形宽度,实际是右下点的x坐标
即矩形高度,实际是右下点的y坐标
即{
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
}
]
}
]
}
到这一步,我们发现这其实就是「绝对定位」,那么其缺点也显而易见了。就是每一块区域都是独立的,相互之间的关系需要你严格设定。
大多数的设计师在布局的时候都是先画一个区域,然后再画一个区域,逐个进行调整。然后带来的问题就是,设计师最讨厌用设计工具做「表格」类的东西。
为什么会这样?
因为设计工具提供的布局方式就是「绝对定位」
根据以上所示,绝对定位里的概念总结起来就只有一个,叫做「坐标」
从上面的结构,我们可以发现几个事情
1
的原点和1.1
的原点是同一个1.2
的宽度等于1
的宽度减去1.1
的宽度1.2.1
和1.2.2
的宽度等于1.2
的宽度我们要完成的是「布局」而非「绘图」,所以我们可以让这件事情变得更加简单一些。我们分步来说明
我们首先来制作一块区域,它的名字叫1
,并且宽度是1024
、高度也是1024
,原点位置为(0,0)
,(0,0)
我们把它设为缺省值,所以不表达。
{
name: 1,
width: 1024,
height: 1024
}
我们现在对其分割,那么这个时候就产生了一个问题,我们是按「水平」方向分割,还是按「垂直」方向分割?因此,我们引入方向(维度)概念。我们简单的定义一下
列
,即在「水平」方向上进行分割行
, 即在「垂直」方向上进行分割那么我们对1
进行水平方向上的分割
{
name: '1',
width: 1024,
height: 1024,
split: 'column'
}
那么分割为几份呢?这时候我们又要引入一个份数的概念,那么我们现在就分成2
份。
{
name: '1',
width: 1024,
height: 1024,
split: 'column',
part: 2
}
这个时候我们意识到,因为是按「水平」方向分割,那么每一块的高度应该是和原来的区域是一样的,相当于每一块都继承了原有区域的高度,但是宽度因为被分割了,所以没法继承。
我们得考虑宽度的分配,现在我们将宽度进行了分割,那么每一块占原来宽度的多少呢?我们假设是均分,每一块就是50%
,那么用一个值去表达
{
name: '1',
width: 1024,
height: 1024,
split: 'column',
part: 2,
distribute: 0.5
}
上面的结构可以表述为
有一块
1024
*1024
的区域,拆分成2
列,每一列的宽度是1024
*0.5
同理如果是按「垂直」方向分割的话,则高度需要被分割,而宽度可以继承
那如果不等分怎么办?我们的一个distribute
不足矣表达,那么如果distribute
是一个数组的话,数组的length
其实就代表了part
的数量,而且可以直接用对象的key
来标识方向,所以可以形式上简化一下
{
name: '1',
width: 1024,
height: 1024,
columns: [0.4, 0.6]
}
但是以上结构会遇到所有的distribute
之和不是1
,既可能出现大于1
,也可能出现小于1
的情况。这时候我们引入一个「等份」的概念,叫做fr
。1fr
代表空间的1等份
比如说,我们把空间分成3块,第一块占3fr
,第二块占2fr
那么这两块区域的实际宽度是
这下原来的数据结构可以表达为
{
name: '1',
width: 1024,
height: 1024,
columns: [1fr, 1fr]
}
并不是所有的情况都是按等份来分配的。比如某块区域,我们想让它占据一个绝对数值的宽度。
{
name: '1',
width: 1024,
height: 1024,
columns: [200, 1fr, 2fr]
}
这个时候的计算其实是,把200
宽度的区域从原来的里面扣除,在进行等份的计算
两块区域的实际宽度是
那如果全都是绝对数值会怎样
比如有一个区域宽是800
,我们分3
列,每一列宽度是200
{
name: '1',
width: 800,
height: 800,
columns: [200, 200, 200],
}
这个时候其实就是分开处理了,1
本身是一个「区域」,3
列构成的是另一个「区域」
如果分割的区域之和超出了被分割的区域又该怎么样呢?
其实和上面是一样的,实际上原有的「区域」和实际的「被分割区域」是分开处理的
总结一下就是
当「区域分割规则」都是「绝对数值」的时候,那就直接「划定区域」,而不去做「分割」
前端小伙伴这个时候都笑了,写了这么废话不就是CSS的grid么
我们把最初的数据结构来一次梳理
{
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'
}
]
}
]
}
是不是很简单,但是上面其他的所有部分是不是很复杂?
这就是为什么我们平时用别人做好的东西很简单,但是如果自己实现却无从下手的原因。
以上的结构我们再还原到坐标系画线的模式中,看看是怎样的
先画一个最大的「区域」1
lineTop: (0,0)(width,0) //(0,0)(1024,0)
lineRight: (width,0)(width,height) //(1024,0)(1024,1024)
lineBottom: (width,height)(0,height) //(1024,1024)(0,1024)
lineLeft: (0,height)(0,0) //(0,1024)(0,0)
画1.1
lineTop: (0,0)(width*1/(1+1),0) //(0,0)(512,0)
lineRight: (width*1/(1+1),0)(width*1/(1+1),height) //(512,0)(512,1024)
lineBottom: (width*1/(1+1),height)(0,height) //(512,1024)(0,1024)
lineLeft: (0,height)(0,0) //(0,1024)(0,0)
画1.2
lineTop: (width*1/(1+1),0)(width,0) //(512,0)(1024,0)
lineRight: (width,0)(width,height) //(1024,0)(1024,1024)
lineBottom: (width,height)(width*1/(1+1),height) //(1024,1024)(512,1024)
lineLeft: (width*1/(1+1),height)(width*1/(1+1),0) //(512,1024)(512,0)
画1.2.1
lineTop: (width*1/(1+1),0)(width,0) //(512,0)(1024,0)
lineRight: (width,0)(width,height*1/(1+1)) //(1024,0)(1024,512)
lineBottom: (width,height*1/(1+1))(width*1/(1+1),height*1/(1+1)) //(1024,512)(512,512)
lineLeft: (width*1/(1+1),height*1/(1+1))(width*1/(1+1),0) //(512,512)(512,0)
画1.2.2
lineTop: (width*1/(1+1),height*1/(1+1))(width,height*1/(1+1)) //(512,512)(1024,512)
lineRight: (width,height*1/(1+1))(width,height) //(1024,512)(1024,1024)
lineBottom: (width,height)(width*1/(1+1),height) //(1024,1024)(512,1024)
lineLeft: (width*1/(1+1),height)(width*1/(1+1),0) //(512,1024)(512,0)
我们发现「布局」的根本方式是「绝对坐标」,在之上抽象出了「栅格(grid)」。非常幸运的是,CSS
已经提供了这层抽象。
如果做成vue
组件的话,形式化方面不想采用近似iview
的<row>
</col>
嵌套结构,而是只有一个area
组件,通过一个数据结构来完成area
的递归
基于以上的观察,我们可以构建一个组件叫做area
,那么它应该包含以下属性
可选
可选
但是我们之前说过,在另一个方向上存在继承关系,那么应该避免同时设置columns
和rows
,另外考虑到宽度和高度可以从上级继承,我们做一次改进
可选
可选
columns
rows
按grid进行递归,如果发现没有grid,就停止递归
不是所有的浏览器都支持grid,保险的做法是使用百分比
但是我们就不选择兼容方案了,激进一点
用边框表达,还是用颜色表达
平台 | 中文字体 | 西文字体 |
---|---|---|
阿里云 | PingFang SC , Microsoft YaHei |
Helvetica ,Arial ,Verdana |
腾讯云 | PingFangSC-Light ,hiragino sans gb , microsoft yahei ui , microsoft yahei ,simsun |
helvetica neue ,arial ,sans-serif |
<template>
<button @click="foo">Button</button>
</template>
<script>
export default {
name: 'my-button',
methods:{
foo () {
this.$emit('onClick')
}
}
}
</script>
<template>
<my-button @onClick="handleClick"></my-button>
</template>
<script>
import myButton from '@/components/myButton'
export default {
components: { myButton }
methods: {
handleClick () {
console.log('boo')
}
}
}
</script>
<template>
<button @click="onClick">Button</button>
</template>
<script>
export default {
name: 'my-button',
props: {
onClick: {
type: Function
}
}
}
</script>
<template>
<my-button :onClick="handleClick"></my-button>
</template>
<script>
import myButton from '@/components/myButton'
export default {
components: { myButton }
methods: {
handleClick () {
console.log('boo')
}
}
}
</script>
父组件
不知道事件什么时候会发生,因为事件的监听和处理都在子组件
里
子组件
不知道父组件
在事件发生后干什么,因为干什么是执行的父组件
里的method
子组件
要求父组件
提供一个Callback函数,当事件发生时子组件
去回调父组件
提供的Callback函数
两种方式的运行结果是一样的,写法上和理解上有差别。
父组件能够监听到事件的发生,并且决定在事件发生时做什么
在props的情况下
父组件不知道事件发生与否,只能指令子组件在发生时做什么处理
组件内部抛一个事件
onChange (value) {
this.$emit('OnChange', value)
}
外部使用时监听,并且执行一个回调
<tag @onChange="log"></tag>
<script>
methods: {
log (value) {
console.log(value)
}
}
</script>
返回
value
<tag @onChange="log('change')"></tag>
<script>
methods: {
log (value) {
console.log(value)
}
}
</script>
返回
'change'
既返回原始value,也返回字符串
'change' + value
模板式写法无法做到,只能这样写
<tag @onChange="log"></tag>
<script>
methods: {
log (value) {
console.log ('change' + value)
}
}
</script>
使用函数式组件也许可以,还没有尝试
把onChange本身传递的value
作为函数的参数向下传递
<tag @onChange="(value) => log('change'+value)"></tag>
<script>
methods: {
log (value) {
console.log (value)
}
}
</script>
返回
'change' + value