关于grid布局的一些思考

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
|- 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'
        }
      ]
    }
  ]
}

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>

3. 结构演化

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

假设我们做这样一个布局

area

3.1 宽高结构

{
  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 坐标结构

{
  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 宽高和坐标

{
  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)我们把它设为缺省值,所以不表达。

{
  name: 1,
  width: 1024,
  height: 1024
}

3.4.2 引入分割方向概念

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

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

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

{
  name: '1',
  width: 1024,
  height: 1024,
  split: 'column'
}

3.4.3 引入份数概念

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

{
  name: '1',
  width: 1024,
  height: 1024,
  split: 'column',
  part: 2
}

3.4.4 在另一方向上的继承

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

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

{
  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来标识方向,所以可以形式上简化一下

{
  name: '1',
  width: 1024,
  height: 1024,
  columns: [0.4, 0.6]
}

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

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

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

  • 第一区域宽度:

  • 第二区域宽度:

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

{
  name: '1',
  width: 1024,
  height: 1024,
  columns: [1fr, 1fr]
}

3.4.6 绝对数值

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

{
  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'
        }
      ]
    }
  ]
}

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

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

3.4.7 回归到坐标系

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

先画一个最大的「区域」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)

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 模板

  • 左右
  • 上下
  • 圣杯

关于收藏夹功能的思考

我之前有个习惯,喜欢收集很多资料以备不时之需,但日后也不会真的回溯。这两天又重新使用Evernote,但是总不明白怎么用,用来收集备份资料总感觉要重蹈覆辙,资料收集了不看是病,得治。后来想明白了,把Evernote当便签用,印证好记性不如烂笔头。由此想到一个信息网站常用的收藏功能,简单思考了一下 继续阅读关于收藏夹功能的思考

如果能够收集到世界上所有人的梦

做梦网

最初决定开始做做梦网的时候并没有做很多深入的考虑,只是想看看自己对于互联网产品的一些想法是否真的能够付诸实践。和绝大多数人一样,最开始我对做梦网有着超出实际的预期(当然现在也还是有同样想法),也曾努力地推广和尝试各种方式试图提高网站的各项指标。甚至还绞尽脑汁的想过商业模式以及数据推演。

今年的某个时间,看了网易公开课里的詹姆斯·卡梅隆在TED上的一次分享。我突然改变之前对做梦网的一切想法。为什么不能做的更伟大一点?为什么要那么的功利去做这件事情?之前考虑的那么多是我最初的目的吗?

我开始重新勾勒做梦网的蓝图,之前的什么SEO、推广、运营、产品、用户体验的概念统统从我脑海中消失。现在我只需要去思考,它的价值究竟在哪里。如果,这么一个网站能够收集到全中国甚至全世界所有人的梦,并按照一定的信息结构进行梳理。那是否能够让我们对人类、对整个社会产生更加深刻的认知?现在一切都还不那么明朗,但我愿意去试一试。

最近做梦网正在进行改版,预计今年年底会和大家见面。希望它能够达到我前面说到的愿景。