Dragging a card on mobile is VERY sluggish and laggy #64
Labels
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Grandiras/Ponente#64
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
🤖 On it — follow along in the action run; the reply will link the full session transcript.
📜 Live transcript — updating every ~5 minutes while the task runs.
Pushed to
claude/issue-64.What was wrong
Two SortableJS options that are unremarkable on a desktop are wired to the raw input stream on touch.
scroll(autoscroll) andemptyInsertThreshold(empty-list drop detection) both take their coordinates fromdragoverwhen a native HTML5 drag is available. On a touch device there is no native drag, so the library binds the same handlers topointermove/touchmoveinstead — and a current phone samples touches 120–240 times a second:scrollopens every call withdocument.elementFromPoint, which flushes style and layout for the whole board and then hit-tests it.emptyInsertThresholdputs every empty Sortable into a document-level handler that measures each one — and the pagination bar contributes one permanently-empty Sortable per column.That asymmetry is why the same drag was fine on a desktop and crawled on a phone. In a CPU profile of a drag,
elementFromPointalone was the single largest identifiable cost, ~21% of non-idle time.What changed
emptyInsertThreshold: 0on the drop tiles. It cost them a 20px halo; the tiles are 64px tall while a drag is on.scroll: falseeverywhere, with the capability reimplemented insortable-interop.jsasdragScroll(~45 lines). It reads nothing from the DOM while the finger is moving: containers are measured when the drag starts, the move handler only records a coordinate, and a rAF loop compares the two. It also listens fordragover, because a mouse drag fires no pointer move at all while it is in flight — without that, desktop autoscroll would have silently died.will-change: transformon.card-dragging. The library rewrites that clone's transform every frame; off its own compositor layer that repaints two soft shadows every frame with it.What I verified
No container engine or dotnet in this runner, so the compose stack and the Playwright suite could not be started. Instead I built an offline harness that loads the repo's real
app.css,sortable.min.js,sortable-interop.jsandboard-pagination.jsinto a board of 4 columns × 14 cards, and drives real touch drags over CDP against an emulated Pixel 7 (confirmednativeDraggable === false, i.e. the actual mobile code path).Traced, 10× CPU throttle, median of 7 runs:
elementFromPointFunctional checks, all passing: reorder within a column still fires
OnCardMovedat the right index; a drop on a pagination tile still moves the card and strands nothing in the bar; a card held at a column's bottom edge scrolls it; a card held at the board's right edge scrolls sideways; the frame loop stops on drop; and adragover-only coordinate drives the loop (the desktop path).Tests and docs
board.spec.jspins both Sortable options and thewill-changepromotion.mobile.spec.jsgains aTouch draggroup that drives a real CDP touch drag and asserts a held card scrolls its column — the behaviour that moved from the library into this repo. It creates its own card so teardown can reclaim it. These two are unrun — they need the compose stack. Every assertion in them was checked true against the real files in the harness, but the live-board wiring around them has not executed.CLAUDE.mdrecords the desktop/touch asymmetry, whydragScrollreads nothing per move, and the two non-obvious rules in it.One thing I deliberately did not do:
animation: 0measured another ~15% off script time, but it costs the reorder slide on every platform, and the wins above don't need it.⏱ 43m 38s · 126 turns · tokens: 210.3k in / 96.2k out (+17328.1k cache-read)
📜 Full session transcript
→ Opened PR: #67