Figma 时间选择器与高精度时段拾取系统(TimePicker & Time Range Picker Architecture)组件架构实战:从时/分/秒三列独立平滑滚动视口、12/24小时制切换、预设常用时段胶囊(Preset Chips)到 Variables 状态机与 Dev Mode 前端 Day.js / Ant Design TimePicker 代码交付

2026 年 11 月 14 日

1. 业务痛点与时间拾取系统(TimePicker)的设计系统工程挑战 #

在企业级 B 端办公协同(会议室预约、排班排课)、工业制造流水线计划、金融交易清算窗口、日志检索与运维监控等高商业价值(High-CPC)场景中,时间维度的精确定位与区间过滤是最基础也是最频繁的交互操作。相较于日历日期选择器(聚焦“年-月-日”二维网格排布),时间选择器(TimePicker)与时段范围拾取系统(Time Range Picker) 专注于“时、分、秒(HH:mm:ss)”的一维连续流选择,承载着更高密度的交互约束与无障碍输入要求。

然而在日常 UI/UX 设计与组件库建设中,时间选择器往往容易暴露出严重的工程缺陷:

  1. 多列联动视口与遮罩拓扑混乱:时间选择浮层通常包含“时(00-23)”、“分(00-59)”、“秒(00-59)”三列并行滚动视口。如果缺乏规范的 Figma Auto Layout 自动布局 容器约束,在切换 12/24 小时制(增加 AM/PM 列)或省略“秒”列时,整个弹层组件极易发生宽度崩塌与错位;
  2. 中心聚焦高亮带与渐变半透明遮罩缺失:优秀的滚轮选择器通常依赖中心聚焦带(Active Focus Band)与上下边缘线性渐变遮罩(Linear Gradient Fade Overlay)来营造“物理滚轮透视”景深感。静态设计稿若采用随意堆叠的矩形,会导致在深浅模式切换时产生硬切边缘与反光穿透;
  3. 步长(Step: 5m/15m)与时段禁用规则不严密:在会议室预约场景中,时间通常按 15 分钟或 30 分钟步长递增;而在排班系统中,非工作时间或已约满时段必须置灰禁用(Disabled)。若设计规范中没有清晰解耦禁用态变体与“无法点击”、“Tooltip 悬浮解释”,会导致前端实现时缺少交互边界防护;
  4. 范围选择器(Time Range Picker)起止时段闭环逻辑缺失:用户选择“开始时间”与“结束时间”时,极易出现“结束时间早于开始时间”的逻辑悖论。缺少动态原型逻辑校验与关联错误反馈提示,迫使研发团队自行推导校验时机;
  5. 研发交付与前端生态标准脱节:前端工程师在生产环境广泛采用现代时间处理库(Day.js、date-fns、Luxon)以及 headless 组件库(Radix UI、Ant Design TimePicker、React Aria)。若 Figma 中未交付标准的格式化掩码(HH:mm:ss vs hh:mm A)、快捷预设胶囊(Quick Presets)与键盘无障碍访问规范,研发交付效率将大打折扣。

常见时间拾取交互方案工程对比 #

评估维度 键盘掩码输入框 (Mask Input) 独立滚轮列选择器 (Scroll Column TimePicker) 双列时间段区间拾取器 (Time Range Picker)
主要交互方式 键盘直接键入数字,支持光标分段跳跃 鼠标滚轮滑动滚动、点击选项高亮 左右双面板联动,或单输入框弹出双列选区
适用业务场景 极客场景、代码控制台、极速键入录入 标准表单、会议预约、日常时间设置 日志时段排查、计划排期、跨度筛选
输入容错机制 强依赖正则表达式校验与实时纠错掩码 仅展示合法有效选项,天然物理防呆 增加“起止时间跨度(Start < End)”动态拦截
空间占用体积 仅占用单行输入框面积(32px ~ 40px) 弹出中等浮层(约 200px * 260px) 弹出宽面板或双面板(约 380px * 300px)
Figma 组件设计重点 表单状态矩阵与错误提示 三列独立 Auto Layout 视口与渐变蒙版 起止时段变量绑定与快捷预设胶囊(Chips)

2. 三列滚轮视口 Auto Layout 几何拓扑与居中聚焦带架构 #

构建高工业级的时间选择器浮层,首要任务是在 Figma 中建立结构严密的几何容器模型。通过三层嵌套的 Auto Layout,确保列的动态增减(时/分/秒/制度)与自适应对齐。

┌────────────────────────────────────────────────────────────────────────┐
│ TimePicker Dropdown Overlay (Auto Layout Vertical, Width: 224px)       │
├────────────────────────────────────────────────────────────────────────┤
│ Quick Presets Bar: [Now] [In 15m] [In 1h] [All Day] (Auto Layout Wrap) │
├────────────────────────────────────────────────────────────────────────┤
│ Columns Viewport Wrapper (Horizontal, Gap: 0, Height: 224px, Clip)     │
│  ┌───────────────┬───────────────┬───────────────┬──────────────────┐  │
│  │ Hours Column  │ Minutes Column│ Seconds Column│ Period (AM/PM)   │  │
│  │ (Width: 56px) │ (Width: 56px) │ (Width: 56px) │ (Width: 56px)    │  │
│  │  ┌─────────┐  │  ┌─────────┐  │  ┌─────────┐  │  ┌─────────┐     │  │
│  │  │   08    │  │  │   20    │  │  │   00    │  │  │   AM    │     │  │
│  │ ─┼─────────┼──┼──┼─────────┼──┼──┼─────────┼──┼──┼─────────┼─   │  │
│  │▶ │ 09 (Act)│  │▶ │ 30 (Act)│  │▶ │ 00 (Act)│  │▶ │ PM (Act)│   │  │
│  │ ─┼─────────┼──┼──┼─────────┼──┼──┼─────────┼──┼──┼─────────┼─   │  │
│  │  │   10    │  │  │   45    │  │  │   15    │  │  │         │     │  │
│  │  └─────────┘  │  └─────────┘  │  └─────────┘  │  └─────────┘     │  │
│  └───────────────┴───────────────┴───────────────┴──────────────────┘  │
│  * Center Focus Band Overlay: Absolute Positioned, Height: 32px, Box   │
├────────────────────────────────────────────────────────────────────────┤
│ Bottom Action Bar: [Now Button]                        [Confirm / OK]  │
└────────────────────────────────────────────────────────────────────────┘

2.1 三列时/分/秒(HH / mm / ss)独立视口布局规范 #

  1. 单列滚动容器(Time Column Scroll Container):
    • 布局方向设置为垂直 Auto Layout(Vertical Direction);
    • 尺寸规格:单列宽度设为 Fixed width = 56px,内边距设置 Padding: Top = 96px, Bottom = 96px(上下留出两个选项高度的留白,确保第 0 项与最后一项能够滚入正中央聚焦带);
    • 容器高级属性中开启 Clip content(裁切内容),并在 Prototype 面板配置 Overflow: Vertical scrolling。
  2. 多列组合外壳(Columns Viewport Wrapper):
    • 水平 Auto Layout,间距 Gap = 0px;
    • 列与列之间插入垂直分割线组件(Width = 1px, Fill height,绑定变量 border-subtle: #F1F5F9);
    • 通过 Component Properties(组件属性)为每一列提供布尔开关:showHours: true、showMinutes: true、showSeconds: false(默认隐藏秒列)、use12Hours: false。

2.2 中心高亮选择带(Active Focus Band)与上下渐变蒙版 #

为了打造身临其境的视差聚焦质感,采用“绝对定位”技术在滚轮视口上方悬浮两层光学滤镜:

  • 中心高亮聚焦带(Active Focus Band):
    • 规格:宽度 100% (Fill container),高度 Fixed = 32px;
    • 勾选 Figma 绝对定位(Absolute Positioning),设置垂直居中约束(Top: 50%,向上偏移 -16px);
    • 样式填充弱色强调底色(color-brand-subtle: #EFF6FF,暗黑模式为 #1E293B),上下描边配置 1px solid #3B82F6(或中性细线),圆角设为 4px;
    • 设置 Pointer-events: none 概念(在研发交付中标注),避免物理遮挡下层点击事件。
  • 上下景深渐变遮罩(Gradient Fade Overlay):
    • 在视口容器顶部与底部各放置一个高 40px 的渐变矩形;
    • 顶部渐变:从实色背景(#FFFFFF / 100%)线性渐变过渡至完全透明(#FFFFFF / 0%);
    • 底部渐变:从完全透明反向过渡至实色背景;
    • 这层渐变有效遮蔽了边缘滚动文字,使用户的视觉焦点自然汇聚于中央被高亮的时间数值。

2.3 单元格时间刻度项(Time Column Item)状态矩阵 #

单项刻度组件 Time-Picker-Item 统一规格为高 32px、宽 100% 的水平居中 Auto Layout 容器,其状态矩阵如下:

交互状态 适用条件 文字字重与字号 文字颜色 Token (Light / Dark) 背景样式与视觉特征
Normal (常规) 非当前选中的备选项 14px / Regular (400) text-secondary: #475569 / #94A3B8 透明背景,光标指针(Pointer)
Hover (悬浮) 鼠标移动到该刻度上方 14px / Medium (500) text-primary: #0F172A / #F8FAFC 填充浅中性底色 bg-hover: #F8FAFC
Selected (选中) 当前滚轮居中的激活值 14px / SemiBold (600) brand-default: #2563EB / #60A5FA 居于中心聚焦带内,字重强化
Disabled (禁用) 非法时段(如已预约、跨度超限) 14px / Regular (400) text-disabled: #CBD5E1 / #475569 置灰且带禁止图标(Not-allowed),不可交互

3. 复杂时间交互扩展:12/24小时制、步长阶梯与预设胶囊 #

企业级产品需要无缝适配出海跨国团队、多语言本地化以及精细化业务管控诉求。结合站内 Figma 多语言国际化(i18n)与出海设计系统架构实战 的设计原则,扩展模块设计如下:

3.1 12 小时制(AM/PM)与 24 小时制动态切换 #

  • 24 小时制(24-Hour Military Format):小时列范围为 00 ~ 23,为国际化业务、数据分析报表和系统运维后台的标准格式;
  • 12 小时制(12-Hour AM/PM Format):小时列范围收缩为 01 ~ 12,同时在最右侧追加 AM / PM 分段控制器或第 4 列滚轮;
  • 组件属性控制:在父级组件上建立 format 变体属性,取值为 "HH:mm:ss"、"HH:mm"、"hh:mm A",通过实例属性面板一键切换,布局自动增减列宽并保持居中。

3.2 步长间隔阶梯(Step: 5 min / 15 min / 30 min) #

在会议室或工单预约场景中,如果分钟列展示 00 到 59 共 60 个刻度,用户需耗费大量滚动手势:

  • 稀疏阶梯模式:配置 minuteStep 属性(默认 1,可选 5、10、15、30);
  • 当 minuteStep = 15 时,分钟列仅呈现 00、15、30、45 四个选项,大幅减少滚动负荷,从设计层面杜绝用户选出诸如“14:23”这类非标准会议时间。

3.3 禁用时段(Disabled Hours & Minutes)与业务约束边界 #

时间选择器必须提供精细的禁用判定规则:

  • 整点禁用:当某小时(如早晨 00:00 ~ 08:00)超出营业范围,小时列中的 00 ~ 07 直接渲染为 Disabled 状态;
  • 级联动态禁用:当选定某特定小时后,如果分钟只有前半小时可用,分钟列中的 31 ~ 59 需联动变为 Disabled;
  • 禁用提示 Tooltip:当用户将鼠标悬停在禁用项上时,利用站内 Figma 气泡确认框与危险操作二次确认系统架构实战 类似的浮层插槽,轻量弹出“该时段不在排班服务范围内”。

3.4 常用快捷时段胶囊(Quick Preset Chips)与底部操作栏 #

在浮层顶部或底部配置高频快捷入口,能极大提升高频操作场景的满意度:

  • 快捷胶囊栏(Preset Chips):水平 Auto Layout 弹性换行容器,包含“此刻(Now)”、“15 分钟后”、“下个整点(Next Hour)”、“工作时间(09:00 - 18:00)”等轻量 Pills 胶囊;
  • 底部操作栏(Bottom Actions):包含“此刻”快捷文字链与右侧“确定(Confirm)”主行动按钮。支持在弹层选定时间后二次确认,防止在数据大屏或复杂报表中因无意触摸引发频繁的重绘刷新。

4. Figma Variables 与交互组件驱动的时段选择动态原型 #

为了在向研发交付或跨职能评审时具备逼真的交互体验,我们运用 Figma Variables(变量)与交互组件构建起止时间联动的完整状态机。

                    ┌──────────────────────────────────────────┐
                    │          Time Range Picker Input         │
                    │   [ 09:00:00 ]   至   [ 18:00:00 ]       │
                    └─────────────────────┬────────────────────┘
                                          │ On Click [Start Input]
                                          ▼
                    ┌──────────────────────────────────────────┐
                    │      Dropdown 1: Select Start Time       │
                    │   Selected: 09:00:00                     │
                    │   Var: startTimeValue = "09:00:00"       │
                    └─────────────────────┬────────────────────┘
                                          │ Auto-switch to End Input
                                          ▼
                    ┌──────────────────────────────────────────┐
                    │      Dropdown 2: Select End Time         │
                    │   * Restrict: Hours < 09 are Disabled    │
                    │   Selected: 18:00:00                     │
                    │   Var: endTimeValue = "18:00:00"         │
                    └──────────────────────────────────────────┘

4.1 变量模型设计(Variables Collection: TimePicker) #

在 Figma 变量管理器中创建 TimePicker-Runtime 集合,定义以下响应式变量:

变量名称 (Variable Name) 变量类型 (Type) 初始值 (Default) 交互绑定与原型作用
selected-hour String "09" 存储当前选中的小时数字符串("00" ~ "23")
selected-minute String "30" 存储当前选中的分钟数字符串("00" ~ "59")
selected-second String "00" 存储当前选中的秒数字符串
start-time-text String "09:30:00" 区间范围选择器中的开始时间合成文本
end-time-text String "18:00:00" 区间范围选择器中的结束时间合成文本
is-time-range-valid Boolean true 布尔状态,指示起止时间跨度是否合法(StartTime <= EndTime)
dropdown-visible Boolean false 控制时间选择器浮层的展开与收起

4.2 起止时段跨度校验状态机(Validation Logic) #

针对时间范围拾取器(Time Range Picker),在原型中配置条件动作:

  1. 输入框文本动态回填:将输入框中的文字图层绑定到变量 start-time-text;
  2. 选择刻度触发赋值:当用户在小时列点击 10、在分钟列点击 00 时,设置动作 Set variable: start-time-text = "10:00:00";
  3. 合法性条件拦截:
    • 添加 Conditional 条件逻辑:
    • if (end-time-text < start-time-text):触发错误提示变量 is-time-range-valid = false,输入框边框瞬间变为报警红(border-error: #EF4444),并在底部提示“结束时间不能早于开始时间”;
    • else:设置 is-time-range-valid = true,自动高亮确定按钮。详细高级条件逻辑设置可参考站内 Figma 高级原型与条件逻辑实战。

5. Dev Mode 开发模式与现代前端代码交付(Day.js / Ant Design / Radix UI) #

在 Figma Dev Mode 开发模式 下,清晰的组件结构能直接映射为现代前端成熟的日期时间工具链与可访问性(a11y)规范。

5.1 数据协议与 Day.js 格式化标准对照 #

与研发团队拉通标准的时间字符串协议,统一基于 ISO 8601 或标准格式化令牌(Formatting Tokens):

业务场景 推荐格式化模板 (Day.js Token) 示例数据格式 适用场景说明
标准 24 小时制 HH:mm:ss 14:30:45 绝大多数企业级管理后台、日志监控
常规简版时间 HH:mm 09:15 会议预约、办公打卡、排班排课
国际化 12 小时制 hh:mm:ss A 02:30:00 PM 欧美出海产品、多语言本地化场景
毫秒精细时间 HH:mm:ss.SSS 18:05:22.482 金融高频交易、高并发链路追踪

5.2 现代 React + Day.js + CSS Scroll Snap 生产级实现 #

以下为映射 Figma 三列独立滚动视口与 CSS 滚动捕捉(Scroll Snap)的前端生产级核心实现代码:

import React, { useState, useRef, useEffect } from 'react';
import dayjs, { Dayjs } from 'dayjs';

interface TimePickerProps {
  value?: Dayjs;
  onChange?: (newTime: Dayjs) => void;
  minuteStep?: number;
  format?: 'HH:mm:ss' | 'HH:mm';
}

export const FigmaTimePicker: React.FC<TimePickerProps> = ({
  value = dayjs(),
  onChange,
  minuteStep = 1,
  format = 'HH:mm:ss'
}) => {
  const [selectedTime, setSelectedTime] = useState<Dayjs>(value);

  // 生成时、分、秒选项数组
  const hours = Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0'));
  const minutes = Array.from(
    { length: Math.ceil(60 / minuteStep) },
    (_, i) => String(i * minuteStep).padStart(2, '0')
  );
  const seconds = Array.from({ length: 60 }, (_, i) => String(i).padStart(2, '0'));

  const handleSelect = (unit: 'hour' | 'minute' | 'second', val: number) => {
    const updated = selectedTime.set(unit, val);
    setSelectedTime(updated);
    if (onChange) onChange(updated);
  };

  return (
    <div
      className="figma-timepicker-overlay"
      style={{
        display: 'inline-flex',
        flexDirection: 'column',
        width: format === 'HH:mm:ss' ? '224px' : '160px',
        background: '#FFFFFF',
        borderRadius: '8px',
        boxShadow: '0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1)',
        border: '1px solid #E2E8F0',
        overflow: 'hidden',
        position: 'relative',
        userSelect: 'none'
      }}
    >
      {/* 快捷预设时段 */}
      <div
        className="preset-bar"
        style={{
          display: 'flex',
          gap: '6px',
          padding: '8px 12px',
          borderBottom: '1px solid #F1F5F9',
          background: '#F8FAFC'
        }}
      >
        <button
          type="button"
          onClick={() => handleSelect('hour', dayjs().hour())}
          style={{
            fontSize: '12px',
            padding: '2px 8px',
            borderRadius: '12px',
            border: '1px solid #CBD5E1',
            background: '#FFFFFF',
            cursor: 'pointer'
          }}
        >
          此刻
        </button>
      </div>

      {/* 列视口组合容器 */}
      <div
        className="columns-wrapper"
        style={{
          display: 'flex',
          height: '224px',
          position: 'relative'
        }}
      >
        {/* 中心高亮聚焦带 (32px 高度居中) */}
        <div
          className="active-focus-band"
          style={{
            position: 'absolute',
            top: '50%',
            left: 0,
            right: 0,
            height: '32px',
            transform: 'translateY(-50%)',
            backgroundColor: '#EFF6FF',
            borderTop: '1px solid #3B82F6',
            borderBottom: '1px solid #3B82F6',
            pointerEvents: 'none',
            zIndex: 1
          }}
        />

        {/* 小时列 */}
        <ScrollColumn
          items={hours}
          selectedVal={selectedTime.format('HH')}
          onItemClick={h => handleSelect('hour', parseInt(h, 10))}
        />

        {/* 分钟列 */}
        <ScrollColumn
          items={minutes}
          selectedVal={selectedTime.format('mm')}
          onItemClick={m => handleSelect('minute', parseInt(m, 10))}
        />

        {/* 秒列 (根据 format 控制显示) */}
        {format === 'HH:mm:ss' && (
          <ScrollColumn
            items={seconds}
            selectedVal={selectedTime.format('ss')}
            onItemClick={s => handleSelect('second', parseInt(s, 10))}
          />
        )}
      </div>

      {/* 底部操作确认栏 */}
      <div
        style={{
          display: 'flex',
          justifyContent: 'flex-end',
          padding: '8px 12px',
          borderTop: '1px solid #F1F5F9',
          background: '#FFFFFF'
        }}
      >
        <button
          type="button"
          onClick={() => onChange && onChange(selectedTime)}
          style={{
            backgroundColor: '#2563EB',
            color: '#FFFFFF',
            fontSize: '13px',
            fontWeight: 500,
            padding: '4px 12px',
            borderRadius: '4px',
            border: 'none',
            cursor: 'pointer'
          }}
        >
          确定
        </button>
      </div>
    </div>
  );
};

// 单列平滑滚轮子组件,支持 CSS Scroll Snap
const ScrollColumn: React.FC<{
  items: string[];
  selectedVal: string;
  onItemClick: (val: string) => void;
}> = ({ items, selectedVal, onItemClick }) => {
  return (
    <div
      style={{
        flex: 1,
        overflowY: 'auto',
        scrollSnapType: 'y mandatory',
        padding: '96px 0',
        textAlign: 'center',
        zIndex: 2,
        scrollbarWidth: 'none'
      }}
    >
      {items.map(item => {
        const isSelected = item === selectedVal;
        return (
          <div
            key={item}
            onClick={() => onItemClick(item)}
            style={{
              height: '32px',
              lineHeight: '32px',
              fontSize: '14px',
              scrollSnapAlign: 'center',
              fontWeight: isSelected ? 600 : 400,
              color: isSelected ? '#1D4ED8' : '#64748B',
              cursor: 'pointer',
              transition: 'color 0.15s ease'
            }}
          >
            {item}
          </div>
        );
      })}
    </div>
  );
};

6. 企业级时间选择器设计系统自检清单与落地避坑指南 #

为保证时间选择器在跨团队交付中的工程严谨性,设计团队应严格按照以下六大维度进行自查核对:

自检维度 核心验收项 达标规范标准 常见设计误区与避坑建议
1. 视口几何约束 上下留白与内边距配置 单列滚动容器必须配置 Padding: Top/Bottom = 96px 缺少上下内边距导致首尾数字无法滚至正中央激活线
2. 格式与制度适配 12/24 小时制灵活切换 提供独立 use12Hours 属性,增减列时整体对齐平稳 强行在 24 小时制界面塞入 AM/PM,造成时间定义冲突
3. 步长与性能优化 分钟步进阶梯(minuteStep) 支持按 5m/15m/30m 过滤选项,减轻滚动检索成本 不分场景死板输出 60 个刻度,导致会议预约效率极低
4. 范围闭环校验 起止时段跨度逻辑防御 具备 EndTime < StartTime 的错误反馈视觉态 仅设计静态正常态,未定义起止时间冲突时的警示规范
5. 光学景深设计 中心聚焦带与渐变半透明遮罩 聚焦带设为绝对定位,上下边缘辅以平滑线性渐变 使用实色纯白硬色块遮挡,导致深色主题下产生白边瑕疵
6. Dev Mode 交付 变量命名与标准时间格式 图层规范命名为 Time-Column-Item 并标明对应属性 图层命名随意,导致工程师无法快速理清三列结构关系

通过建立规范的 Auto Layout 独立视口、结合 Figma Variables 驱动起止跨度校验,并将时间令牌无缝映射至 Day.js 与现代化前端组件库,团队能从源头打造坚固、直观、高可用的一流时间选择体验。

❓ 常见问题与答疑 (FAQ)

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

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