钻取过渡

用于层次导航的前进/后退钻取过渡

钻取过渡

钻取过渡清晰地表达了信息层次结构中的层级间导航。屏幕横向滑动的效果让用户进入或退出新层级,直观地传达当前位置和移动方向。

演示

Loading demo...

UX 原则

何时使用

钻取过渡为父子关系层次导航而优化。

内容关系适用性

콘텐츠 관계적합성설명
无关内容不适合独立部分之间
同级内容同层级内容之间不自然
层次内容最适合列表→子列表、概览→详情

主要用例

  • 列表 → 子列表:从分类到子分类的移动
  • 概览 → 详情:从仪表板进入详细分析
  • 设置 → 子设置:从主设置到具体选项
  • 文件浏览器:导航文件夹结构

为什么这样工作

  1. 空间隐喻:横向滑动表达"进入"和"退出"
  2. 层次感知:重叠的屏幕可视化信息深度
  3. 手势友好:与移动端边缘滑动手势自然集成
  4. 上下文保持:部分可见的前一屏保持路径认知

动效设计

钻取前进(Enter):
1. 新屏幕 → 从右侧100%位置开始
2. 运动中 → 新屏幕进入,推出现有屏幕
3. 完成 → 新屏幕占满全屏,现有屏幕在-20%

钻取后退(Exit):
1. 当前屏幕 → 从0%位置开始
2. 运动中 → 向右滑出,露出前一屏
3. 完成 → 前一屏恢复,当前屏幕在100%外

基本用法

1. 过渡配置

import { Ssgoi } from '@ssgoi/react';
import { drill } from '@ssgoi/react/view-transitions';

const config = {
  transitions: [
    {
      from: '/categories',
      to: '/categories/*',
      transition: drill({ direction: 'enter' }),
      symmetric: false
    },
    {
      from: '/categories/*',
      to: '/categories',
      transition: drill({ direction: 'exit' })
    }
  ]
};

export default function App() {
  return (
    <Ssgoi config={config}>
      {/* 应用内容 */}
    </Ssgoi>
  );
}

2. 选项配置

interface DrillOptions {
  opacity?: boolean;           // 透明度效果(默认:false)
  direction?: 'enter' | 'exit'; // 钻取方向(默认:'enter')
  spring?: {
    stiffness?: number;         // 弹簧刚度(默认:150)
    damping?: number;           // 阻尼系数(默认:20)
  };
}

实际示例

1. 分类导航

导航嵌套的分类结构:

// 主分类
function Categories() {
  return (
    <div className="p-4">
      <h1 className="text-2xl mb-4">分类</h1>
      <div className="space-y-2">
        {categories.map(cat => (
          <Link
            key={cat.id}
            to={`/categories/${cat.slug}`}
            className="block p-4 bg-white rounded-lg shadow"
          >
            <h3 className="font-semibold">{cat.name}</h3>
            <p className="text-gray-600">{cat.itemCount} 项</p>
          </Link>
        ))}
      </div>
    </div>
  );
}

// 子分类
function SubCategory({ category }) {
  return (
    <div className="p-4">
      <button onClick={() => navigate('/categories')} className="mb-4">
        ← 返回
      </button>
      <h1 className="text-2xl mb-4">{category.name}</h1>
      <div className="grid gap-4">
        {category.items.map(item => (
          <div key={item.id} className="p-4 bg-white rounded-lg">
            {item.name}
          </div>
        ))}
      </div>
    </div>
  );
}

2. 设置菜单

从设置到详细选项:

// 主设置
function Settings() {
  const menuItems = [
    { id: 'profile', label: '个人资料设置', icon: '👤' },
    { id: 'privacy', label: '隐私与安全', icon: '🔒' },
    { id: 'notifications', label: '通知设置', icon: '🔔' },
    { id: 'display', label: '显示设置', icon: '🎨' }
  ];

  return (
    <div className="max-w-lg mx-auto">
      <h1 className="text-2xl p-4 border-b">设置</h1>
      <div className="divide-y">
        {menuItems.map(item => (
          <Link
            key={item.id}
            to={`/settings/${item.id}`}
            className="flex items-center p-4 hover:bg-gray-50"
          >
            <span className="text-2xl mr-4">{item.icon}</span>
            <span className="flex-1">{item.label}</span>
            <span></span>
          </Link>
        ))}
      </div>
    </div>
  );
}

// 设置详情
function SettingDetail({ type }) {
  return (
    <div className="max-w-lg mx-auto">
      <div className="flex items-center p-4 border-b">
        <button onClick={() => navigate('/settings')}>←</button>
        <h1 className="text-xl ml-4">{getSettingTitle(type)}</h1>
      </div>
      <div className="p-4">
        {/* 详细设置选项 */}
      </div>
    </div>
  );
}

3. 文件浏览器

文件夹结构导航:

function FileExplorer({ path }) {
  const config = {
    transitions: [
      {
        from: '/files/*',
        to: '/files/*/*',
        transition: drill({ 
          opacity: true,
          spring: { stiffness: 180, damping: 22 }
        })
      }
    ]
  };

  return (
    <div className="h-screen flex flex-col">
      <div className="p-3 bg-gray-100 text-sm">
        {path.split('/').map((segment, i) => (
          <span key={i}>
            {i > 0 && ' / '}
            <button onClick={() => navigateToLevel(i)}>
              {segment}
            </button>
          </span>
        ))}
      </div>
      <div className="flex-1 overflow-auto p-4">
        {files.map(file => (
          <div
            key={file.id}
            onClick={() => file.isFolder && navigate(file.path)}
            className="flex items-center p-2 hover:bg-gray-50"
          >
            <span className="mr-2">
              {file.isFolder ? '📁' : '📄'}
            </span>
            {file.name}
          </div>
        ))}
      </div>
    </div>
  );
}

高级配置

添加透明度效果

// 内容淡入淡出钻取
drill({ 
  opacity: true,
  spring: { stiffness: 120, damping: 18 }
})

方向自定义

// 钻取进入(默认)
drill({ direction: 'enter' })

// 钻取退出(返回)
drill({ direction: 'exit' })

弹簧物理调整

// 快速钻取
drill({ 
  spring: { stiffness: 200, damping: 25 }
})

// 平滑钻取
drill({ 
  spring: { stiffness: 100, damping: 15 }
})

注意事项

层次一致性

  • 仅在有明确层次结构时使用钻取过渡
  • 同级导航使用滑动或淡入淡出
  • 返回时必须应用反向钻取

性能考虑

  • 大图或复杂布局使用transform优化
  • 移动端利用will-change属性
  • 避免太多元素同时动画

无障碍

  • 减少动效偏好时立即过渡
  • 完整键盘导航支持
  • 屏幕阅读器识别为标准页面过渡
  • 焦点自动移至新页面

最佳实践

✅ 应该做

  • 用于明确的父子关系
  • 与返回手势集成
  • 配合面包屑显示路径
  • 保持一致的方向性

❌ 不应该做

  • 不要在同级或独立部分间使用
  • 不要设置太快的速度
  • 不要同时使用双向钻取
  • 不要在深层次(>5级)过度使用