Figma 企业级分页器(Pagination System)组件架构实战:从折叠算法、极值边界状态机到 Variables 动态翻页与 Dev Mode 前端交付

2026 年 11 月 12 日

在企业级中后台 SaaS、数据看板、CRM 与电商交易系统中,分页器(Pagination System) 是连接海量数据资产与用户操作视口的关键桥梁。几乎所有复杂数据表格(Data Grid)、工单列表和检索结果页都离不开分页器的精确控制。

然而,在许多团队的 Figma 设计规范中,分页器往往沦为“静态占位符”或充斥着严重的工程缺陷:

  1. 机械穷举变体引发变体爆炸:为了表达“第 1 页激活”、“第 5 页激活”、“中间有省略号”、“两端有省略号”等不同状态,机械地创建了几十个庞大的变体切片,导致组件库体积膨胀、设计师挑选属性如履薄冰;
  2. 文本跳动与尺寸塌陷:当页码从个位数(如 3)翻到三四位数(如 128)时,未设置合理的 Auto Layout 自动布局 极值约束,导致页码格子宽度忽大忽小、左右箭头被挤压变形甚至直接换行穿透;
  3. 极值边界状态缺失:仅设计了通用的分页状态,忽略了“处于第 1 页时上一页按钮必须禁用(Disabled)”、“处于尾页时下一页必须禁用”以及“快速跳转输入框异常输入保护”等严谨交互状态机;
  4. 原型演示全靠画板堆砌:由于缺少现代变量(Variables)驱动思维,每演示一次翻页操作就需复制整屏画板,导致原型文件错综复杂,极难维护;
  5. 研发交付语义割裂:工程师在 Dev Mode 开发模式 中看到的只是一排硬编码的色块,无法对齐 Ant Design、Element Plus 或 React 现代组件库的 Props 接口。

本文将从现代组件工程化视角出发,带你完整搭建一套工业级、轻量化且支持动态变量交互的高保真分页器组件系统。


分页器复合架构与原子状态机矩阵 #

一个完整的企业级分页器绝不仅是一串数字,它是由多个功能解耦的子组件协同构成的复合系统:

┌────────────────────────────────────────────────────────────────────────────────────────┐
│ [共 1,280 条]  [ ◄ 上一页 ] [ 1 ] [...] [ 4 ] [ 5 (Active) ] [ 6 ] [...] [ 128 ] [ 下一页 ► ] [ 20条/页 ▼ ] [跳至 [ 5 ] 页] │
│      │               │                                                           │              │
│  数据总量 (Total)   导航控制 (Arrows)            页码项目与折叠省略号 (Items & Ellipsis)       条数选择器 (Size) 跳转输入框 (Jumper) │
└────────────────────────────────────────────────────────────────────────────────────────┘

1. 核心子组件拆解与职责划分 #

子模块 (Sub-component) 界面定位 包含的核心元素与交互能力 约束与对齐规范
Pagination Item (页码项) 原子基石 纯数字文字,提供固定高宽比例与圆角背景 尺寸锁定 32×32px 或 28×28px,居中对齐
Prev / Next Nav (翻页控制) 导航定位 左/右箭头图标,支持文案折叠(纯图标或图标+文字) 必须具备第一页/最后一页的严格 Disabled 禁用锁
Ellipsis / Jump-Prev/Next 省略号折叠 静态 ...,悬停时可转换为双箭头快速步进(+5/-5 页) 必须与常规 Item 保持等宽等高,防止间距突变
Page Size Selector (每页条数) 数据密度 下拉选择器(如 10条/页、20条/页、50条/页) 采用嵌套下拉菜单标准规范,支持弹出浮层
Quick Jumper (快速跳转器) 长列表直达 文本提示“跳至” + 数字输入框(Input)+ “页” 输入框内部支持固定宽度与失焦校验
Total Count (数据总量) 宏观概览 辅助文字(如“共 850 条”或“显示 1-20 条 / 共 850 条”) 固定左侧或右侧,文字遵循中性辅助色 Token

2. 页码单项(Pagination Item)六大交互状态机 #

每个分页数字格子都必须定义完备的状态机闭环:

状态 (State) 背景填充 (Background) 文字/图标颜色 (Text/Icon) 边框 (Border) 交互与光标 (Cursor)
Default (默认非激活) 透明或纯白无底色 中性文本主色(neutral-text-primary) 1px 浅灰色线框或无边框 光标为 pointer,可被点击
Hover (悬停态) 浅色品牌微底色(brand-bg-subtle) 品牌主色(brand-text-accent) 1px 品牌浅色描边 提示可交互性,微弱明度过渡
Active / Current (当前选中) 品牌实心高饱和填充或外发光 纯白反差反白或高亮主色 1px 品牌强调色锁定 挂载 aria-current="page",不可重复点击触发请求
Focus-Visible (键盘聚焦态) 保持当前底色 保持当前文字色 2px 品牌色外环轮廓(Outline Ring) WCAG 2.2 无障碍键盘 Tab 导航强制要求
Disabled (禁用态) 浅灰弱底色(neutral-bg-disabled) 浅灰失效色(neutral-text-disabled) 浅灰失效边框 光标变为 not-allowed,阻止一切点击事件

Auto Layout 尺寸自适应与数字防跳动架构 #

在真实业务场景中,页码数值可能是个位数(1),也可能是三位数乃至五位数(1000)。如果使用默认的 Hug contents,方块宽度会剧烈收缩与膨胀,导致整个底栏不断颤抖抖动。

1. 黄金正方形约束与 Min-Width 极值保护 #

以工业级桌面端标准 Medium 规格为例:

  1. 容器布局:外层 Frame 开启 Auto Layout,水平居中(Align Center),垂直居中(Align Center);
  2. 尺寸防变形配置:
    • 高度(Height):强制锁定为固定值(Fixed 32px);
    • 宽度(Width):设为 Hug contents,并在高级布局设置中添加 Min-Width: 32px(或固定 32px);
    • 圆角(Border Radius):推荐设置为 4px 或 6px,现代 SaaS 亦可设为全圆角胶囊形;
  3. 等宽数字排印(Tabular Figures):
    • 必须在 Typography 高级排版选项(Type Details)中,展开 OpenType 选项并勾选 Tabular figures(等宽对齐数字);
    • 此设置确保字符 1、8、0 拥有完全一致的字形渲染宽度,彻底终结多位数引起的微像素位移跳动。

2. 整体分页栏横向弹性吸附(Responsive Flexbox) #

整套分页器的横向容器配置法则:

  • 主容器开启横向 Auto Layout,间距(Item Spacing / Gap)固定设为 8px;
  • 在列表与数据表格底栏中,将左侧数据总量设为 Hug contents,中间放置弹性空白占位(Spacer / Fill container)或直接将分页器靠右对齐;
  • 确保在响应式断点收缩时,自动隐藏非关键的“快速跳转器”或精简省略号,杜绝产生横向滚动条。

工业级页码折叠算法与 Component Properties 降维 #

在总页数较少时(如总页数 ≤ 7),通常平铺所有页码;而当总页数 > 7 时,业界通用算法规则如下:

Case A (当前页在最左侧):  [1] [2 (Active)] [3] [4] [5] [...] [100]
Case B (当前页在最右侧):  [1] [...] [96] [97] [98 (Active)] [99] [100]
Case C (当前页在居中位置): [1] [...] [49] [50 (Active)] [51] [...] [100]

1. 传统变体爆炸 vs 组件属性解耦对比 #

架构策略 所需变体数量 维护与加载成本 属性修改灵活度 推荐程度
传统全穷举变体法 50+ 个完整切片 极高,每次改色或字体需批量修改 50 处 极差,无法自定义中间特定数字 ❌ 强烈抵制
折叠模式三段式 (Layout Mode) 3 个核心变体(Left / Middle / Right) 低,仅维护 3 组排布模型 良好,兼顾直观展示与结构轻量 ⚠️ 中小项目推荐
Component Properties 槽位动态解耦 仅 1 个标准底板 + 布尔/实例槽位 极低,组件库体积减少 85% 极高,自由开启前置/后置省略号 ✅ 工业级首选

2. 现代 Component Properties 降维配置清单 #

在主分页器组件中,将各个动态单元解耦为现代属性:

  1. 布尔属性(Boolean):
    • showTotal: 控制左侧“共 XX 条”文本可见性;
    • showPageSize: 控制每页条数选择器显隐;
    • showQuickJumper: 控制右侧跳转输入框显隐;
    • hasPrevEllipsis: 控制第 1 页后方的 ... 折叠号;
    • hasNextEllipsis: 控制倒数第 1 页前方的 ... 折叠号;
  2. 实例替换属性(Instance Swap):
    • prevIconSlot: 允许将向左箭头替换为文字“上一页”;
    • nextIconSlot: 允许将向右箭头替换为文字“下一页”;
  3. 文本属性(Text Property):
    • 分别为可见的各个分页单元绑定 pageNumber_1 到 pageNumber_7,设计师在侧边栏即可直接改写数值,无需双击钻入深层图层。

Variables 变量驱动的高保真动态翻页交互实战 #

借助 Figma 原生 Variables 变量与高级原型条件逻辑(Conditionals),我们只需在单画板中即可实现真实可交互的上下翻页与极值锁死功能,告别几十张复制画板的噩梦。

1. 变量集合(Variable Collection)定义 #

创建名为 Pagination_State 的变量集合,录入以下核心字段:

变量名称 (Variable Name) 数据类型 (Type) 初始默认值 (Default Value) 语义与业务作用
currentPage Number 1 当前激活的页码索引
totalPages Number 10 系统的总页数极值上限
totalRecords Number 100 数据总条目数
isFirstPage Boolean true 是否处于第一页(控制 Prev 禁用)
isLastPage Boolean false 是否处于末尾页(控制 Next 禁用)

2. 图层可见性与状态绑定 #

  1. 页码高亮绑定:将页码项的激活外观或指示圆圈的图层可见性(Layer Visibility),绑定条件表达式(例如当 currentPage == 1 时显示 Item 1 的 Active 态,否则显示 Default 态);
  2. 文本绑定:将页码格子中的数字文本直接绑定到变量或动态计算展示;
  3. 禁用锁绑定:将“上一页”箭头的 Disabled 变体绑定至布尔变量 isFirstPage;将“下一页”箭头的 Disabled 变体绑定至布尔变量 isLastPage。

3. 上一页 / 下一页的条件判断动作流(Actions Flow) #

在“下一页”按钮上添加 On click 交互事件,配置如下顺序动作流:

On Click:
  ┌─► Condition: if (currentPage < totalPages)
  │     ├─► Set variable: currentPage = currentPage + 1
  │     └─► Set variable: isFirstPage = false
  │
  └─► Condition: if (currentPage == totalPages)
        └─► Set variable: isLastPage = true

同样,在“上一页”按钮上配置对称的反向动作流:

On Click:
  ┌─► Condition: if (currentPage > 1)
  │     ├─► Set variable: currentPage = currentPage - 1
  │     └─► Set variable: isLastPage = false
  │
  └─► Condition: if (currentPage == 1)
        └─► Set variable: isFirstPage = true

运行原型演示视图,点击下一页,当前页码数值立刻递增,到达第 10 页时下一页按钮瞬间呈现灰度禁用态,无法继续点击;点击上一页平滑递减,回到第 1 页时上一页按钮立即自动锁死。这种真机级别的保真度为可用性测试和利益相关者演示带来了质的飞跃。

更多高阶交互与变量绑定细节,可参考 高级原型与滚动溢出设置 与 Variables 入门指南。


Dev Mode 前端工程化映射与生产代码交付 #

在 Dev Mode 开发模式 中,分页器需要无缝对齐主流前端工程框架(如 React + Ant Design 或 Tailwind CSS),杜绝无意义的绝对定位代码。

1. React + Tailwind CSS 生产级组件实现 #

import React from 'react';
import { ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react';

export interface PaginationProps {
  currentPage: number;
  totalPages: number;
  onPageChange: (page: number) => void;
  showTotal?: boolean;
  totalRecords?: number;
}

export const Pagination: React.FC<PaginationProps> = ({
  currentPage,
  totalPages,
  onPageChange,
  showTotal = true,
  totalRecords = 0,
}) => {
  const isFirst = currentPage <= 1;
  const isLast = currentPage >= totalPages;

  return (
    <nav
      role="navigation"
      aria-label="Pagination Navigation"
      className="inline-flex items-center gap-2 text-sm select-none"
    >
      {showTotal && (
        <span className="text-slate-500 mr-2 text-xs">
          共 {totalRecords.toLocaleString()} 条
        </span>
      )}

      {/* 上一页控制按钮 */}
      <button
        type="button"
        disabled={isFirst}
        onClick={() => !isFirst && onPageChange(currentPage - 1)}
        aria-label="Go to previous page"
        className={`h-8 w-8 inline-flex items-center justify-center rounded border transition-colors ${
          isFirst
            ? 'border-slate-200 text-slate-300 cursor-not-allowed bg-slate-50'
            : 'border-slate-300 text-slate-700 hover:bg-slate-100 active:bg-slate-200'
        }`}
      >
        <ChevronLeft className="w-4 h-4" />
      </button>

      {/* 页码序列渲染示例 */}
      {Array.from({ length: totalPages }, (_, i) => i + 1)
        .slice(0, 5) // 示例截取,实际需配合折叠算法
        .map((page) => {
          const isActive = page === currentPage;
          return (
            <button
              key={page}
              type="button"
              aria-current={isActive ? 'page' : undefined}
              aria-label={`Page ${page}`}
              onClick={() => onPageChange(page)}
              className={`h-8 min-w-[32px] px-2 inline-flex items-center justify-center rounded text-sm font-medium transition-colors ${
                isActive
                  ? 'bg-indigo-600 text-white font-semibold shadow-sm'
                  : 'text-slate-700 hover:bg-slate-100 hover:text-slate-900'
              }`}
            >
              {page}
            </button>
          );
        })}

      {/* 省略号标记 */}
      {totalPages > 5 && (
        <span className="h-8 w-8 inline-flex items-center justify-center text-slate-400">
          <MoreHorizontal className="w-4 h-4" />
        </span>
      )}

      {/* 下一页控制按钮 */}
      <button
        type="button"
        disabled={isLast}
        onClick={() => !isLast && onPageChange(currentPage + 1)}
        aria-label="Go to next page"
        className={`h-8 w-8 inline-flex items-center justify-center rounded border transition-colors ${
          isLast
            ? 'border-slate-200 text-slate-300 cursor-not-allowed bg-slate-50'
            : 'border-slate-300 text-slate-700 hover:bg-slate-100 active:bg-slate-200'
        }`}
      >
        <ChevronRight className="w-4 h-4" />
      </button>
    </nav>
  );
};

2. 无障碍(a11y)交付标准核查 #

  • 导航语义包裹:外层容器在 HTML 语义中必须输出 <nav aria-label="Pagination Navigation">,告知屏幕阅读器当前区域为分页导航;
  • 当前激活态标记:当前所在页的按钮必须标注 aria-current="page",确保盲人用户可精准获知所处位置;
  • 图标按钮文本补全:纯箭头按钮必须补充 aria-label="Go to previous page" 与 aria-label="Go to next page",杜绝仅有无声矢量图形。详细 a11y 规范参见 Figma 无障碍设计合规落地实战。

工业级分页器架构避坑与排障矩阵 #

故障现象 根源剖析 生产级标准修复方案
页码数值变为多位数时格子严重变形 容器宽度设为固定值未留弹性,或字体未开启等宽对齐 容器宽度设为 Hug contents + Min-Width 32px 兜底,字体开启 Tabular figures 等宽数字
折叠省略号与其后方数字挤压在一起 省略号文本使用了普通字体行内排印,左右缺失标准留白 将省略号单独封装为 32×32px 的独立 Frame,内部矢量居中对齐,杜绝字距溢出
点击首页时依然触发翻页请求或报错 交互事件缺少极值条件(Boundary Condition)前置拦截 在原型与代码中严格校验 currentPage > 1,将禁用态组件的鼠标事件设为 pointer-events-none
暗黑模式切换时激活态数字对比度严重超标 激活态使用了硬编码深蓝或深灰,未绑定语义令牌 统一绑定 Design Tokens 标准手册 中的 color/brand/primary 与 color/text/inverse

更多组件变体组织模型与通用原子封装,可参考 创建与使用变体、Figma 通用按钮系统架构实战 与 Figma 复杂数据表格组件实战。

❓ 常见问题与答疑 (FAQ)

高频疑难解析与避坑指南
A

完全不需要。绝大多数核心功能在免费 Starter 计划中均可完整体验与实操练习,无需额外付费购买付费团队版。