4.2 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
本文件为 Claude Code 在本仓库中工作提供指引。请使用中文回复。
项目概述
基于 Leafer-UI 的 React 无限滑动画布组件(InfiniteCanvas)。卡片以网格平铺,支持四向无限环绕、拖拽平移、惯性滑动、响应式布局与点击回调。核心是一个可复用的组件库(src/components/InfiniteCanvas),src/App.tsx 仅是演示用例
技术栈:React 19 · TypeScript · Vite · Leafer-UI · Tailwind CSS v4 · pnpm
Leafer 文档查询:https://context7.com/leaferjs/ai-docs/llms.txt?tokens=10000
工作约定
- 每次改动尽量小,大改动需要先与用户确认
- 不要影响原有逻辑
- 不要过度封装代码
- 代码需要加上中文注释
- 必要时需要更新README.md文件,该文件主要是说明如何使用,不需要说明太多技术细节
常用命令
pnpm install
pnpm dev # 启动 Vite 开发服务器
pnpm build # 先 tsc -b 类型检查,再 vite build 打包
pnpm preview # 预览构建产物
pnpm lint # ESLint 检查全仓库
注意:pnpm build 会先执行 tsc -b,类型错误会中断构建。没有配置测试框架
架构
组件分三层,关注点分离清晰:
- InfiniteCanvas.tsx — 薄壳组件。只创建容器
div(默认填满父级、cursor: grab、touchAction: none)并把containerRef与 props 交给 hook。所有逻辑都不在这里 - useInfiniteSlide.ts — 全部滑动引擎逻辑所在。这是改动行为时唯一需要重点理解的文件
- types.ts —
InfiniteCanvasProps<T>与Responsive<V>类型定义。组件是泛型的,与卡片数据类型T强绑定
滑动引擎的关键设计(useInfiniteSlide)
理解这些约束后再改动,否则容易引入画布重建或闪烁问题:
- 双 effect + ref 模式:结构性 effect 的依赖只有
[containerRef, items, renderCard, background],这四者变化才会销毁并重建整个 Leafer 画布。其余易变的配置(onCardClick/draggable/inertia/friction/ 布局配置)都存在ref中,由一个无依赖的 passive effect 持续同步最新值,避免触发画布重建。新增 prop 时务必判断它该进依赖数组还是 ref renderCard必须引用稳定:它在结构性 effect 依赖里,每次引用变化都会重建画布。调用方必须用useCallback包裹- 签名去重:
layout()会把解析后的布局值拼成sig字符串,与lastSig比对,无变化时直接 return,零开销。ResizeObserver和布局配置变化都通过layoutRef.current?.()触发layout(),靠签名去重避免无谓重建 - 无限环绕用取模
wrap:把每个节点坐标规整到[-size, span - size)区间,使屏幕任意位置都被卡片覆盖。平铺份数(repX/repY)保证总跨度 ≥ 视口 + 一个步距。连续 wrap(而非阈值跳变)消除 1px 闪烁 - Leafer 内置交互全部禁用(
move/zoom/wheel均disabled),改用自定义PointerEvent.DOWN/MOVE/UP实现拖拽与无限环绕 - 惯性滑动:松手时记录最近帧速度
vx/vy,用requestAnimationFrame每帧按frictionRef衰减,低于阈值(0.1)停止 - 点击命中用
PointerEvent.TAP:Leafer 自带拖拽阈值,平移后不会误触发点击 - 清理兼容 StrictMode 双调用:return 的清理函数会断开 ResizeObserver、停止惯性、解绑事件、销毁画布并清空
layoutRef
重要不变量
cardWidth / cardHeight 同时用于网格定位、无限环绕命中判定和传给 renderCard 的 size。renderCard 实际绘制的尺寸必须与传入的 size 一致,否则环绕命中与视觉会错位。响应式场景下务必使用第三个参数 size 来绘制
响应式
布局类 props(columns / cardWidth / cardHeight / gapX / gapY)均为 Responsive<V> 类型,即「固定值」或 (size) => V 函数。函数依容器宽高动态计算,由 hook 内的 resolve() 解析(返回非正数或非 number 时回退默认值)