Grid布局的设计与技术实现

简介: 最近在NoCode项目碰到了对于栅格(Grid)布局组件的处理,对它深入研究了一下,发现还是蛮有意思的,遂以此文记录Grid这种设计概念以及它在Antd组件库中的React技术实现。栅格组件其实是前端开发中经常使用到的一种经典布局、排版组件,用它来实现功能可能是没有问题,但是对于它的设计理念以及React技术实现,了解的人却很少。比如,栅格的基本组成部分是哪些?为什么AntDesign对于Row的gutter属性推荐使用 16+8n px(n是自然数)?为什么常见的栅格系统都是12列或24列?React技术如何实现栅格?诸如此类,都很有意思并且值得去探究。PS:本文会混用栅格和Grid这

什么是栅格(Grid)


定义


首先,栅格是一个设计概念,并且是一个比较经典的平面设计概念,摘一段维基百科的描述:

栅格设计系统(又称网格设计系统、标准尺寸系统、程序版面设计、瑞士平面设计风格、国际主义平面设计风格),是一种平面设计的方法与风格。运用固定的格子设计版面布局,其风格工整简洁,在二战后大受欢迎,已成为今日出版物设计的主流风格之一。

1629年,法王路易十四命令成立一个管理印刷的皇家特别委员会,由数学家尼古拉斯·加宗(Nicolas Jaugeon)担任领导。委员会提出了新字体设计建议:以罗马体为基础,采用方格为设计依据,每个字体方格分为64个基本方格单位,每个方格单位再分成36小格,这样,一个印刷版面就由2304个小格组成。这是世上最早对字体和版面进行科学实验的活动。也是栅格系统的雏形。

20世纪50年代,栅格设计系统终于在前西德与瑞士得到完善。通过瑞士平面设计杂志的宣传,将瑞士苏黎士和巴塞尔两个城市的设计家从20世纪40年代探索的成果全面展示,并影响世界各国,因此也被称为“瑞士平面设计风格”(Swiss Design)。由于这种风格简单明确,传达功能准确,因而很快得到世界范围内的普遍认可,成为战后影响最大的一种平面设计风格,也是国际最流行的风格,因此又被称为“国际主义平面设计风格”(International Typographic Style)。

通俗的理解,栅格布局就是以网格的方式来实现二维排版布局的方式,如下图所示,就是经典的栅格设计风格:


image.png

组成部分


网格原子单位


从微观角度来看,栅格系统会把可视区域划分为一系列规格一致的小网格,这些网格会辅助设计师更规范的排版和布局,这些小网格也是整个栅格系统的最小单位,需要注意的是,对于研发来说一般不会注意到这个网格原子单位的存在。

以AntDesign为代表的大部分设计语言会把网格原子单位定为8,为什么会定为8而不是其他的数字呢?


image.png

如果以4、6、8、10、12作为栅格的原子单位,可以看到目前主流屏幕分辨率和它们的整除关系如下图,可以看出来4是整除率最高的单位,但是4作为原子单位实在是太小,增减看起来差别并不明显,所以在整除率和合适之间寻求一个平衡,选择8作为栅格的原子单位,这也就解释了为什么AntDesign的Grid组件要使用(16+8n)px来作为栅格间隔水槽。


image.png

列(Column)和水槽(Gutter)


上面讲到了栅格的网格原子单位,但是在使用中我们会把整个可视区域划分为若干列,我们会直接声明某个内容区域在横向占了多少列来标识整个内容区域的宽度。

通常我们使用的组件库中的Grid组件会直接把可视区域划分为12列或者24列,那为什么是12和24呢?我还特意查了一下这个问题,得到的解答(知乎)是:

因为12是1,2,3,4,6的最小公倍数,所以12列栅格系统相对较灵活,支持将一行分成1列,2列,3列,4列,6列。若是想要支持5列,那1,2,3,4,5的最小公倍数是60,而60这个数对于栅格系统来说显然太大了。18能均分4列不?24能做的12都能做,所以12是最好的选择。

水槽是相邻两个列宽之间的间隔,用来规范页面中内容间的间距,水槽的值越大,页面中留白部分的面积越多,视觉效果越松散,反之,页面越紧凑。水槽通常设置为定值。

image.png

React组件实现


文章中只是部分代码,完整代码地址:github.com/erdong-fe/t…

为了探究React组件对于Grid的实现,我研读了Antd对于Grid的源码。

React组件对于Grid的实现,关键在于使用一维的Flex布局来模拟二维的效果,它分拆出了两个组件,分别是Row和Col,来实现行和列的摆放布局,所以对于React Grid的研究,重点在于研究Row和Col这两个组件。


Row


Row组件最大的作用在于创建一个Flex布局的Dom容器,并且接收行相关的参数,为了简单起见,我只实现Row接收gutter参数,Row的参数定义如下:

interface RowProps extends React.HTMLAttributes<HTMLDivElement> {
    gutter?: number
}
复制代码

还有一个问题需要注意,比如对于下面这个Grid布局来说,列与列之间有gutter,这个比较好实现,设置列左右的margin就行了,但是对于最左侧和最右侧的列来说,它们的margin-left和margin-right是多余的,所以我们需要在Row的代码里面做调整

调整代码如下:

function Row(props: RowProps) {
// ...
  const rowStyle: React.CSSProperties = {};
  if (gutter && gutter > 0) {
      rowStyle.marginLeft = gutter / -2;
      rowStyle.marginRight = gutter / -2;
  }
// ...
}
复制代码

剩下的就是实现Row代码结构里包含Col、把Row接受的参数透传给Col组件以及相关的样式代码即可,完整代码如下:

/** RowContext文件 **/
import { createContext, Context } from 'react';
export interface RowContextState {
    gutter?: number
}
const RowContext: Context<RowContextState> = createContext({});
export default RowContext;
/** row文件 **/
import React from 'react';
import RowContext from './RowContext';
import './style.scss';
interface RowProps extends React.HTMLAttributes<HTMLDivElement> {
    gutter?: number
}
function Row(props: RowProps) {
    const {
        gutter = 0,
        children
    } = props;
    const rowStyle: React.CSSProperties = {};
    if (gutter && gutter > 0) {
        rowStyle.marginLeft = gutter / -2;
        rowStyle.marginRight = gutter / -2;
    }
    const rowContext = React.useMemo(() => ({ gutter }), [gutter])
    return (
        <RowContext.Provider value={rowContext}> 
            <div className="row" style={{...rowStyle}}>
                { children }
            </div>
        </RowContext.Provider>
    )
}
/** style.scss **/
.row {
    display: flex;
}
复制代码


Col


列组件实现要点是:

读取自身的span参数,并且根据24等分实现自身宽度

从Row组件中读取gutter参数,并且把它变成相应的“水槽”宽度

首先实现读取span参数和实现自身宽度:

/** style.scss **/
@for $index from 1 to 24 {
    .col-#{$index} {
        flex: 0 0 percentage($number: $index / $grid-columns);
    }
}
.col {
    box-sizing: border-box;
}
/** Col文件 **/
interface ColProps extends React.HTMLAttributes<HTMLDivElement> {
    span: number
}
function Col(props: ColProps) {
    const {
        children,
        span
    } = props;
    const classObj = {
        [`col-${span}`]: span !== void 0
    }
    const classes = classNames('col', classObj);
    return (
        <div className={classes}>
            { children }
        </div>
    )
}
复制代码

然后实现从Row组件中读取gutter参数,并且把它变成相应的“水槽”宽度,完整代码如下:

import React, { useContext, CSSProperties } from 'react';
import classNames from 'classnames';
import RowContext from './RowContext';
import './style.scss';
interface ColProps extends React.HTMLAttributes<HTMLDivElement> {
    span: number
}
function Col(props: ColProps) {
    const {
        children,
        span
    } = props;
    const classObj = {
        [`col-${span}`]: span !== void 0
    }
    const classes = classNames('col', classObj);
    const { gutter } = useContext(RowContext);
    const styleObj: CSSProperties = {};
    if (gutter && gutter > 0) {
        const horizontalGutter = gutter / 2;
        styleObj.paddingLeft = horizontalGutter;
        styleObj.paddingRight = horizontalGutter;
    }
    return (
        <div className={classes} style={{...styleObj}}>
            { children }
        </div>
    )
}


总结


前端组件里面除了代码实现以外,也有很多设计思想的体现,理解设计思想或许比单纯会实现代码更有意义

相关文章
|
存储 缓存 弹性计算
阿里云服务器经济型e和通用算力型u1实例规格区别及选择参考
经济型e和通用算力型u1实例是目前阿里云的活动中,除轻量应用服务器之外,活动价格相对于其他云服务器实例规格更低的两个实例规格,很多个人和初创企业用户都会优先考虑选择这两个实例规格的云服务器,那么它们之间有什么区别呢?本文为大家介绍下经济型e和通用算力型u1实例规格的区别及选择参考。
2880 0
阿里云服务器经济型e和通用算力型u1实例规格区别及选择参考
|
10月前
|
存储 缓存 安全
Java 集合篇面试题全面总结及答案解析
本文总结了Java集合框架的核心概念、常见集合类的特性与应用场景,以及开发中可能遇到的问题与解决方案。内容涵盖集合框架的基础接口(如Collection、Set、List、Map)、泛型的优点、线程安全集合类(如ConcurrentHashMap、CopyOnWriteArrayList)、常见集合类的区别(如ArrayList与LinkedList、HashMap与HashTable)等。此外,还详细介绍了如何实现LRU缓存、FIFO队列、优先级队列及栈等数据结构,并提供了相关代码示例。通过本文,读者可以全面掌握Java集合相关的面试知识点及其实际应用技巧。
350 1
|
存储 缓存 算法
分布式锁服务深度解析:以Apache Flink的Checkpointing机制为例
【10月更文挑战第7天】在分布式系统中,多个进程或节点可能需要同时访问和操作共享资源。为了确保数据的一致性和系统的稳定性,我们需要一种机制来协调这些进程或节点的访问,避免并发冲突和竞态条件。分布式锁服务正是为此而生的一种解决方案。它通过在网络环境中实现锁机制,确保同一时间只有一个进程或节点能够访问和操作共享资源。
561 3
|
存储 SQL 关系型数据库
openGauss6.0单点企业版部署_openEuler22.03_x86
openGauss6.0单点企业版部署_openEuler22.03_x86
|
域名解析 安全 网络协议
阿里云SSL证书免费版申请教程,可申请20张DV单域名证书
SSL证书能够为网站和移动应用(APP)及小程序提供数据HTTPS加密协议访问,保障数据的安全。阿里云提供一次性申请20张免费证书额度的服务,满足您的业务需求。
2247 0
阿里云SSL证书免费版申请教程,可申请20张DV单域名证书
|
Prometheus 监控 Cloud Native
基于Docker安装Grafana和Prometheus
Grafana 是一款用 Go 语言开发的开源数据可视化工具,支持数据监控和统计,并具备告警功能。通过 Docker 部署 Grafana 和 Prometheus,可实现系统数据的采集、展示和告警。默认登录用户名和密码均为 admin。配置 Prometheus 数据源后,可导入主机监控模板(ID 8919)进行数据展示。
1162 4
|
机器学习/深度学习 人工智能 自然语言处理
一文讲懂大模型推理技术细节
本文介绍了大模型推理在自然语言处理(NLP)领域的原理与应用。大模型推理利用如GPT、BERT等预训练模型,通过深度学习中的Transformer结构和自注意力机制,实现文本分类、情感分析等多种任务。文章提供了使用Hugging Face的Transformers库进行文本分类的示例代码,并展望了大模型推理技术未来的发展潜力。
|
XML 数据可视化 数据格式
camunda-modeler(5.9.0)介绍及下载
camunda-modeler(5.9.0)介绍及下载
1840 1
|
Arthas 监控 应用服务中间件
HSF Serialize response error on provider side
项目组的应用在HSF Consumer调用HSF Provider时遇到异常。问题源于HSF Provider端序列化响应数据时发生的错误,具体为`com.taobao.hsf.com.caucho.hessian.io.ContextSerializerFactory.getCustomSerializer`方法中的`Class.forName`调用抛出了`NullPointerException`。通过Arthas工具的`watch`命令监控并分析异常堆栈,发现异常发生在尝试获取自定义序列化器的过程中。
1289 1