Per user request: split the development process into per-topic docs under docs/dev-log/. Each entry follows the same structure: What was asked → Problems → How solved Files: - README.md index - 01-initial-scope-and-design.md - 02-ui-primitives-api-design.md - 03-monorepo-scaffold.md - 04-ui-primitive-impl.md - 05-drag-and-drop.md - 06-three-games.md - 07-bugfixes-round-1.md - lessons-learned.md all gotchas consolidated - decisions.md locked decisions with dates Total: 1071 lines across 10 files.
3.3 KiB
3.3 KiB
05 · 拖拽 + 落点判定
做什么
实现 Card 拖出、Zone 接收、onDrop 触发游戏 move。验证端到端:
- 拖单张牌
- 多选(Shift+click)后拖一组
- 跨 zone 拖动
遇到什么问题
问题 1:Playwright 拖拽总 timeout — "subtree intercepts pointer events"
dragstart 不会从被遮挡的 SVG 子元素触发。<rect> / <text> 等会"吃掉"事件。
解决:把 SVG 包在 <div style="pointer-events:none"> 里再 dangerouslySetInnerHTML:
dangerouslySetInnerHTML={{
__html: `<div style="pointer-events:none;width:100%;height:100%;">${svg}</div>`,
}}
问题 2:React StrictMode + Client 双重挂载 → state 翻倍 + React duplicate key
Client(...) 内部用了全局 store;StrictMode 双重挂载让 store 注册两次,state 出现两次(桌上有 4 张牌而非 2 张)。
解决:
Client(...)用useMemo(() => Client({...}), [game])包裹,避免每次 render 重新创建main.tsx暂时移除<StrictMode>(在 boardgame.io 的 React client 升级前)
问题 3:多选拖拽只拖了一张
Zone 没把 selectedIds 传给 Card,Card dragstart 退化为只带自己的 id。
解决:Zone 渲染 Card 时传 cardIds={selected ? Array.from(selectedIds) : undefined},Card 内部 cardIds ?? [card.id] 兜底。
问题 4:拖拽的多张牌在桌面上重叠
DragTest 手牌 fan 默认 spacing=30 < 卡宽 63,点击被上层卡遮挡。
解决:DragTest 显式设 layout: { fan: 0, spacing: 70 }。
问题 5:拖拽到非 drop target zone 时被误判
例如拖到 hand zone(没配 dropMove)会被当作落到自身。
解决:Zone 拖拽前先 if (!isDropTarget) return; 检查。
怎么解决
HTML5 拖拽 + 自定义 MIME
const DRAG_MIME = 'application/x-tts-cards';
e.dataTransfer.setData(DRAG_MIME, JSON.stringify({
cardIds,
fromZoneId: props.fromZoneId,
}));
e.dataTransfer.effectAllowed = 'move';
Zone 接收流程
const handleDragOver = (e) => {
if (!isDropTarget) return;
if (!e.dataTransfer.types.includes(DRAG_MIME)) return;
e.preventDefault(); // 关键:允许 drop
e.dataTransfer.dropEffect = 'move';
};
const handleDrop = (e) => {
e.preventDefault();
const raw = e.dataTransfer.getData(DRAG_MIME);
const payload = JSON.parse(raw);
onDrop?.(payload.cardIds, payload.fromZoneId, def.id);
};
Board 协调
const handleDrop = (cardIds, fromZoneId, toZoneId) => {
const target = ui.zones.find(z => z.id === toZoneId);
if (!target?.dropMove || !onMove) return;
const args = target.dropArgs
? target.dropArgs(cardIds, fromZoneId, toZoneId)
: [cardIds, fromZoneId, toZoneId];
onMove(target.dropMove, args);
setSelectedIds(new Set()); // 拖完清选择
};
验证
- 拖 6♠ → 桌(hand=4, table=1)✓
- Shift+click 选 3 张(2♠/4♠/6♠)+ 拖 2♠ → 全部移到桌(hand=3♠/5♠, table=2♠/4♠/6♠)✓
成果
- Commit
2b492d2 feat(ui): HTML5 drag-and-drop + multi-select - Commit
8953554 fix(ui): drag pointer-events, StrictMode compat, multi-drag(后续 bug 修复)
关联
- 06-three-games.md — 用 DragTest 验证拖拽
- 07-bugfixes-round-1.md — 拖拽 bug 修复
- lessons-learned.md §7-12 — 拖拽相关踩坑