Files
ttstd-infinite-canvas/design-research-folder-card.md

656 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 画布列表卡片设计调研报告
## 一、图片风格分析
### 核心设计元素识别
通过对提供的文件夹卡片图片分析,识别出以下关键设计特征:
#### 1. **玻璃拟态(Glassmorphism)效果**
- 半透明背景:使用 `backdrop-filter: blur()` 实现背景模糊
- 磨砂玻璃质感:白色半透明叠加层(rgba(255,255,255,0.1) ~ 0.15)
- 边框高光:顶部和左侧有微妙的白色边框(1px solid rgba(255,255,255,0.18))
#### 2. **渐变网格背景(Gradient Mesh)**
- 每个卡片顶部有独特的渐变色带(红橙、蓝青、橙红等色系)
- 渐变呈现流动的、非线性的网格状分布
- 类似 macOS Ventura 的壁纸风格,具有深度和立体感
- 渐变从顶部向下过渡到白色/透明
#### 3. **多层阴影系统**
- **主阴影**:较大的扩散阴影 `0 8px 32px rgba(0,0,0,0.12)`
- **环境阴影**:模拟物理深度 `0 2px 8px rgba(0,0,0,0.06)`
- **内阴影**:卡片内部微妙的阴影增强立体感
- 悬停时阴影加深并向上提升:`transform: translateY(-4px)`
#### 4. **圆角处理**
- 大圆角设计:约 `16px - 20px` (r-2xl ~ r-3xl)
- 所有元素保持一致的圆角系统
- 内部按钮使用更小的圆角(8-12px)
#### 5. **布局结构**
- **三态展示**:
1. 正常态(左上):紧凑文件夹样式 + 悬浮按钮
2. 堆叠态(右侧):多层卡片堆叠效果,展示深度
3. 展开态(左下):完整信息展示 + 时间戳
#### 6. **交互反馈**
- 悬停提升效果(Y轴 -4px)
- 鼠标跟随的光泽效果(Glare)
- 3D 倾斜动画(轻微的 rotateX/rotateY)
- 按钮悬浮显示/隐藏
---
## 二、当前项目状态分析
### 现有组件架构
#### 1. **CanvasProjectCard** (`canvas-project-card.tsx`)
```typescript
// 当前结构
<article className="app-canvas-project-card">
<div className="app-canvas-project-preview">
<button className="canvas-project-preview-button">
<ProjectPreview /> // 图片或节点预览
</button>
<span className="canvas-project-select">复选框</span>
<div className="canvas-project-cover-meta">节点数量</div>
</div>
<div className="app-canvas-project-body">
<h2>标题</h2>
<div className="canvas-project-stats">时间/类型</div>
</div>
</article>
```
**当前特点**:
- 扁平化设计,缺少层次感
- 简单的边框和阴影
- 预览区域占比大(aspect-video)
- 无玻璃拟态效果
- 无渐变背景装饰
#### 2. **CometCard** (`comet-card.tsx`)
**已有能力**:
- ✅ 3D 倾斜动画(rotateX/rotateY)
- ✅ 鼠标跟随效果
- ✅ 光泽层(glare)
- ✅ 悬停提升动画
- ✅ 平滑的弹簧动画
**缺少**:
- ❌ 玻璃拟态背景
- ❌ 渐变网格装饰
- ❌ 多层阴影系统
---
## 三、Aceternity UI 调研
### 相关组件清单
#### 1. **3D Card Effect**
- 组件路径:`/components/3d-card-effect`
- 功能:鼠标跟随的3D倾斜效果
- 与 CometCard 功能重叠,我们已有类似实现
#### 2. **Card Hover Effect**
- 组件路径:`/components/card-hover-effect`
- 功能:悬停时的边框光晕和背景渐变
- 可借鉴:边框渐变的实现方式
#### 3. **Background Gradient**
- 组件路径:`/components/background-gradient`
- 功能:动态渐变背景
- **重要**:可用于卡片顶部的渐变网格效果
#### 4. **Meteors**
- 组件路径:`/components/meteors`
- 功能:流星划过的动画效果
- 可选:为卡片增加动态装饰
#### 5. **Glass Morphism**
- 虽然 Aceternity 没有专门的玻璃拟态组件
- 但在多个卡片组件中使用了 `backdrop-blur` 和半透明背景
- 需要自行实现
---
## 四、设计方案
### 方案 A:渐进式升级(推荐)
**目标**:在现有 CanvasProjectCard 基础上,逐步添加玻璃拟态和渐变效果。
#### 4.1 布局设计
**网格布局**:
```css
/* 响应式网格 */
.canvas-library-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: var(--space-6); /* 24px */
padding: var(--space-6);
}
/* 断点优化 */
@media (min-width: 640px) {
/* 2列:640px - 1024px */
grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 1024px) {
/* 3列:1024px - 1440px */
grid-template-columns: repeat(3, 1fr);
}
@media (min-width: 1440px) {
/* 4列:>1440px */
grid-template-columns: repeat(4, 1fr);
}
```
**每行显示数量**:
- 移动端(<640px):1列(自适应宽度)
- 平板(640-1024px):2列
- 桌面(1024-1440px):3列
- 大屏(>1440px):4列
#### 4.2 卡片尺寸
```typescript
// 卡片结构
<article className="canvas-card-folder">
// 宽高比:16:10 (比现有的16:9更接近文件夹比例)
// 最小宽度:280px
// 最大宽度:360px(防止在大屏上过度拉伸)
</article>
```
**尺寸规范**:
- **宽度**:280-360px(由网格自动控制)
- **高度**:自适应(由内容决定)
- **预览区高度**:aspect-[16/10] (~175-225px)
- **内容区高度**:~80px(标题 + 元信息)
#### 4.3 视觉效果分层
```
[层级从底到顶]
├─ 1. 基础背景(白色/暗色,根据主题)
├─ 2. 渐变网格层(顶部装饰)
├─ 3. 玻璃拟态层(backdrop-blur + 半透明)
├─ 4. 内容层(文字、图标、预览)
├─ 5. 边框高光层(顶部和左侧)
└─ 6. 光泽层(hover时的glare效果)
```
---
## 五、CSS 实现方案
### 5.1 玻璃拟态基础样式
```css
.canvas-card-glass {
position: relative;
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(12px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.18);
border-radius: var(--r-2xl); /* 16px */
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.12),
0 2px 8px rgba(0, 0, 0, 0.06),
inset 0 1px 0 rgba(255, 255, 255, 0.1);
transition: transform 0.3s cubic-bezier(0.34, 1.56, 0.64, 1),
box-shadow 0.3s ease;
}
/* 暗色主题 */
.dark .canvas-card-glass {
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(255, 255, 255, 0.12);
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.4),
0 2px 8px rgba(0, 0, 0, 0.2),
inset 0 1px 0 rgba(255, 255, 255, 0.08);
}
.canvas-card-glass:hover {
transform: translateY(-4px);
box-shadow:
0 16px 48px rgba(0, 0, 0, 0.18),
0 4px 12px rgba(0, 0, 0, 0.1),
inset 0 1px 0 rgba(255, 255, 255, 0.15);
}
```
### 5.2 渐变网格装饰
```css
.canvas-card-gradient-mesh {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 120px;
border-radius: var(--r-2xl) var(--r-2xl) 0 0;
overflow: hidden;
pointer-events: none;
z-index: 0;
}
.canvas-card-gradient-mesh::before {
content: '';
position: absolute;
inset: 0;
background:
radial-gradient(circle at 20% 30%, rgba(255, 100, 80, 0.6) 0%, transparent 50%),
radial-gradient(circle at 80% 20%, rgba(100, 180, 255, 0.5) 0%, transparent 50%),
radial-gradient(circle at 50% 70%, rgba(255, 180, 100, 0.4) 0%, transparent 60%);
filter: blur(40px);
opacity: 0.8;
}
.canvas-card-gradient-mesh::after {
content: '';
position: absolute;
inset: 0;
background: linear-gradient(180deg,
rgba(255,255,255,0) 0%,
rgba(255,255,255,0.95) 80%
);
}
/* 每张卡片随机不同的渐变色 */
.canvas-card-gradient-mesh[data-variant="1"]::before {
background:
radial-gradient(circle at 25% 25%, rgba(255, 50, 100, 0.7) 0%, transparent 50%),
radial-gradient(circle at 75% 30%, rgba(80, 120, 255, 0.6) 0%, transparent 50%);
}
.canvas-card-gradient-mesh[data-variant="2"]::before {
background:
radial-gradient(circle at 30% 20%, rgba(0, 200, 180, 0.6) 0%, transparent 50%),
radial-gradient(circle at 70% 40%, rgba(100, 255, 150, 0.5) 0%, transparent 50%);
}
.canvas-card-gradient-mesh[data-variant="3"]::before {
background:
radial-gradient(circle at 20% 35%, rgba(255, 150, 0, 0.7) 0%, transparent 50%),
radial-gradient(circle at 80% 25%, rgba(200, 50, 80, 0.6) 0%, transparent 50%);
}
```
### 5.3 完整卡片样式
```css
.canvas-card-folder {
position: relative;
width: 100%;
height: 100%;
min-height: 280px;
cursor: pointer;
isolation: isolate;
}
.canvas-card-folder-inner {
position: relative;
width: 100%;
height: 100%;
background: rgba(255, 255, 255, 0.08);
backdrop-filter: blur(12px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.18);
border-radius: var(--r-2xl);
overflow: hidden;
box-shadow:
0 8px 32px rgba(0, 0, 0, 0.12),
0 2px 8px rgba(0, 0, 0, 0.06);
transition: all 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
.canvas-card-folder:hover .canvas-card-folder-inner {
transform: translateY(-4px) scale(1.01);
box-shadow:
0 16px 48px rgba(0, 0, 0, 0.18),
0 4px 12px rgba(0, 0, 0, 0.1);
border-color: rgba(255, 255, 255, 0.25);
}
/* 预览区域 */
.canvas-card-preview {
position: relative;
width: 100%;
aspect-ratio: 16 / 10;
overflow: hidden;
z-index: 1;
}
.canvas-card-preview img,
.canvas-card-preview video {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s ease;
}
.canvas-card-folder:hover .canvas-card-preview img,
.canvas-card-folder:hover .canvas-card-preview video {
transform: scale(1.05);
}
/* 内容区域 */
.canvas-card-content {
position: relative;
padding: var(--space-4);
z-index: 1;
}
.canvas-card-title {
font-size: var(--fs-body);
font-weight: 600;
line-height: 1.4;
color: var(--color-neutral-900);
margin-bottom: var(--space-2);
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.dark .canvas-card-title {
color: var(--color-neutral-50);
}
.canvas-card-meta {
display: flex;
align-items: center;
gap: var(--space-2);
font-size: var(--fs-caption);
color: var(--color-neutral-600);
}
.dark .canvas-card-meta {
color: var(--color-neutral-400);
}
/* 悬浮按钮 */
.canvas-card-action-button {
position: absolute;
bottom: var(--space-3);
right: var(--space-3);
width: 40px;
height: 40px;
display: grid;
place-items: center;
background: rgba(0, 0, 0, 0.6);
backdrop-filter: blur(8px);
border-radius: var(--r-lg);
color: white;
opacity: 0;
transform: translateY(8px);
transition: all 0.25s ease;
z-index: 10;
}
.canvas-card-folder:hover .canvas-card-action-button {
opacity: 1;
transform: translateY(0);
}
.canvas-card-action-button:hover {
background: rgba(0, 0, 0, 0.8);
transform: scale(1.1);
}
```
---
## 六、组件实现方案
### 6.1 新建组件:CanvasFolderCard
```tsx
// web/src/components/canvas/canvas-folder-card.tsx
import { motion } from "motion/react";
import { Plus, MoreHorizontal } from "lucide-react";
import { CometCard } from "@/components/ui/aceternity/comet-card";
import type { CanvasProject } from "@/stores/canvas/use-canvas-store";
interface CanvasFolderCardProps {
project: CanvasProject;
variant?: number; // 1-3,决定渐变色方案
onClick?: () => void;
onAction?: () => void;
}
export function CanvasFolderCard({
project,
variant = 1,
onClick,
onAction
}: CanvasFolderCardProps) {
return (
<CometCard
className="canvas-card-folder"
glare={true}
rotateDepth={3}
translateDepth={3}
onClick={onClick}
>
<div className="canvas-card-folder-inner">
{/* 渐变网格装饰 */}
<div
className="canvas-card-gradient-mesh"
data-variant={variant}
aria-hidden="true"
/>
{/* 预览区域 */}
<div className="canvas-card-preview">
<ProjectPreview project={project} />
</div>
{/* 内容区域 */}
<div className="canvas-card-content">
<h3 className="canvas-card-title">{project.title}</h3>
<div className="canvas-card-meta">
<span>{project.nodes.length} 节点</span>
<span aria-hidden="true">·</span>
<time dateTime={project.updatedAt}>
{formatTime(project.updatedAt)}
</time>
</div>
</div>
{/* 悬浮按钮 */}
<motion.button
className="canvas-card-action-button"
onClick={(e) => {
e.stopPropagation();
onAction?.();
}}
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.95 }}
aria-label="更多操作"
>
<MoreHorizontal className="size-5" />
</motion.button>
</div>
</CometCard>
);
}
// 预览组件保持原有逻辑
function ProjectPreview({ project }: { project: CanvasProject }) {
// ... 复用现有的 ProjectPreview 逻辑
}
function formatTime(value: string) {
// ... 复用现有的时间格式化逻辑
}
```
### 6.2 集成到现有页面
```tsx
// 在 pages/home/index.tsx 或 pages/canvas/index.tsx 中
import { CanvasFolderCard } from "@/components/canvas/canvas-folder-card";
// 替换现有的 CanvasProjectCard
<div className="canvas-library-grid">
{canvasProjects.map((project, index) => (
<CanvasFolderCard
key={project.id}
project={project}
variant={(index % 3) + 1} // 循环使用3种渐变方案
onClick={() => navigate(`/canvas/${project.id}`)}
onAction={() => handleMore(project)}
/>
))}
</div>
```
---
## 七、技术要点
### 7.1 性能优化
```typescript
// 1. 渐变色方案预计算
const GRADIENT_VARIANTS = [
{ id: 1, colors: ['rgba(255,50,100,0.7)', 'rgba(80,120,255,0.6)'] },
{ id: 2, colors: ['rgba(0,200,180,0.6)', 'rgba(100,255,150,0.5)'] },
{ id: 3, colors: ['rgba(255,150,0,0.7)', 'rgba(200,50,80,0.6)'] },
];
// 2. 使用 CSS 变量动态注入
<div
style={{
'--gradient-color-1': GRADIENT_VARIANTS[variant].colors[0],
'--gradient-color-2': GRADIENT_VARIANTS[variant].colors[1],
}}
/>
// 3. backdrop-filter 回退方案
@supports not (backdrop-filter: blur(12px)) {
.canvas-card-folder-inner {
background: rgba(255, 255, 255, 0.95); /* 完全不透明 */
}
}
```
### 7.2 无障碍支持
```tsx
// 完整的 ARIA 标签
<article
role="button"
tabIndex={0}
aria-label={`画布:${project.title},${project.nodes.length}个节点`}
onClick={onClick}
onKeyDown={(e) => e.key === 'Enter' && onClick?.()}
>
{/* ... */}
</article>
```
### 7.3 暗色主题适配
```css
/* 自动适配当前主题 */
@media (prefers-color-scheme: dark) {
.canvas-card-folder-inner {
background: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.12);
}
.canvas-card-gradient-mesh::after {
background: linear-gradient(180deg,
rgba(10, 10, 15, 0) 0%,
rgba(10, 10, 15, 0.95) 80%
);
}
}
```
---
## 八、实施建议
### 阶段 1:基础重构(1-2天)
1. ✅ 创建 `CanvasFolderCard` 组件
2. ✅ 实现玻璃拟态样式
3. ✅ 集成 `CometCard` 实现3D效果
4. ✅ 调整网格布局为新的响应式方案
### 阶段 2:视觉增强(2-3天)
1. ✅ 添加渐变网格装饰层
2. ✅ 实现多层阴影系统
3. ✅ 优化悬停动画和过渡
4. ✅ 添加光泽层(glare effect)
### 阶段 3:细节打磨(1-2天)
1. ✅ 暗色主题适配
2. ✅ 无障碍测试和优化
3. ✅ 性能测试(backdrop-filter 的性能影响)
4. ✅ 跨浏览器兼容性测试
### 阶段 4:用户测试(1天)
1. ✅ A/B 测试(新旧卡片对比)
2. ✅ 收集用户反馈
3. ✅ 根据反馈微调
---
## 九、风险和注意事项
### 9.1 性能风险
- **backdrop-filter 性能消耗**:在大量卡片(50+)时可能影响滚动性能
- 解决方案:使用虚拟滚动或懒加载
- 回退方案:低性能设备禁用 backdrop-filter
### 9.2 兼容性
- **Safari < 15**:backdrop-filter 支持不完整
- 解决方案:检测支持度,提供回退样式
### 9.3 可访问性
- 确保玻璃拟态效果不影响文字对比度(WCAG AA 标准:4.5:1)
- 光泽效果不应干扰屏幕阅读器
---
## 十、参考资源
### 设计灵感
- macOS Ventura 文件夹设计
- iOS 16+ 小组件玻璃拟态
- Figma Community: Glassmorphism UI Kit
### 技术文档
- [MDN: backdrop-filter](https://developer.mozilla.org/en-US/docs/Web/CSS/backdrop-filter)
- [CSS Tricks: Glassmorphism](https://css-tricks.com/glassmorphism/)
- [Aceternity UI Components](https://ui.aceternity.com/components)
### 现有代码
- `/web/src/components/canvas/canvas-project-card.tsx`
- `/web/src/components/ui/aceternity/comet-card.tsx`
- `/web/src/styles/globals.css` (token system)
---
## 总结
这套设计方案结合了:
1. **玻璃拟态**的现代感和层次感
2. **渐变网格**的视觉吸引力和品牌差异化
3. **3D交互**的沉浸感(已有CometCard支持)
4. **响应式布局**的灵活性(1-4列自适应)
建议采用**渐进式升级**策略,先实现核心玻璃拟态效果,再逐步添加渐变装饰和高级交互,确保每个阶段都能交付可用版本。