Jigsaw Puzzle JavaScript: Keep Geometry and Placement State in Agreement

You drop a picture piece into the wrong slot, press Escape, and the progress count still increases. Or the piece lands correctly, then snaps back when pointer capture is released.

These bugs come from treating every movement event as a completed placement.

A jigsaw puzzle JavaScript design needs two contracts: complementary interlocking shapes and committed piece locations. Make a small six-piece puzzle first, using an original SVG picture, one canonical slot per piece, and the same placement rules for pointer and keyboard input.

The local HTML/JavaScript/SVG example follows the design below. Check its rendered seams and interactions in the intended host before using it.

Sunriver · Six-piece jigsaw
Sunriver · Six-piece jigsaw
Open the full example ↗

Define six pieces with real shared edges

Use an original abstract picture with a sun, river, and hill. Draw it once in a normalized 300 × 200 SVG coordinate space, with three columns and two rows. The base cells are 100 × 100; internal interlocking outlines extend across their shared boundaries. Outer perimeter edges remain flat.

Piece Canonical slot Picture region
P01 S01 Top left
P02 S02 Top middle
P03 S03 Top right
P04 S04 Bottom left
P05 S05 Bottom middle
P06 S06 Bottom right

Store each internal edge once. For example, the edge shared by P01 and P02 runs along their top-row boundary at x = 100, with a tab/socket curve between its endpoints. One piece traverses that shared path forward; its neighbor traverses it in reverse. They must use the same curve, not separately guessed bumps.

There are four vertical shared-edge segments—two column boundaries in each row—and three horizontal segments along the row boundary. Check all seven pairs. Separate random paths for neighboring pieces can create gaps, overlapping tabs, or impossible sockets.

This is an interlocking-picture puzzle. A grid of six rectangular image tiles would not meet its geometry contract. Keep orientation fixed; rotations and free-moving clusters are outside this small version's scope.

Clip one picture without resizing each fragment

MDN explains that an SVG clipping path limits painted content, while clipping does not change the underlying bounding box. It also notes that clipped-away regions do not receive pointer events by default. These properties matter when inspecting both piece visuals and hit regions. MDN: clipPath

Use each piece outline to clip the same global-coordinate picture. Do not resize the entire picture separately inside each piece's bounding rectangle. That would put the sun or river at inconsistent scales when the pieces return to their canonical locations.

Apply a group's transform to move a piece into the tray or follow a drag. At its correct board location, restore the canonical picture alignment. Keep tabs visible in the tray rather than clipping them again to the rectangular base cell.

Show a reference picture and an equivalent text description. Distinct piece and slot labels provide an alternative to recognizing the illustration, and the native source retains the original picture and shared-edge definitions.

Separate transient movement from committed placement

Track each piece ID's committed location, each slot's occupant, selected piece, active pointer ID, and the drag's prior committed location. The moving visual is temporary; it does not change the completed-placement count.

Use one placement function for every input method. It validates the piece/slot pair, rejects occupied or incorrect slots, commits only the canonical pairing, and computes progress from distinct correctly placed IDs. A correctly placed piece is locked until Reset.

Action Required committed result
P02 dropped on S01 Reject; restore prior location; count unchanged
P02 placed on S02 Commit P02/S02 once; lock P02
Five distinct correct pieces placed 5 of 6; incomplete
All six distinct pieces in their own slots 6 of 6; complete
Duplicate piece ID encountered Invalid state; do not count a seventh piece
Reset Six pieces in tray; 0 of 6; no active transaction

Incrementing a counter for every successful-looking event risks counting the same piece twice. Derive progress from the validated locations instead. Completion needs the entire unique mapping, not six events or six DOM nodes that happen to resemble pieces.

Make pointer release and cancellation different transactions

MDN documents pointerup, pointercancel, and capture release, including capture being released after pointerup or cancellation. Use those lifecycle distinctions to design the transaction. MDN: Pointer Events

Allow one active drag pointer. Events from another pointer must not steal its selected piece or commit a second placement. Record the active pointer and prior location when the drag begins.

On the matching pointerup, evaluate the target. Commit a valid placement or restore the prior location for an invalid one. Clear the active transaction before completing capture cleanup. A subsequent lostpointercapture event must not roll back a placement that was already committed.

On pointercancel or Escape during an active drag, restore the prior committed location, clear drag and selection state, and release any owned capture. Count stays unchanged. Unexpected capture loss while an unfinished transaction is still active should use that same cancellation path; capture loss after cleanup should have no placement effect.

Because placed pieces are locked in this version, a new drag normally starts from the tray. Keeping prior committed location explicit still prevents temporary visual movement from masquerading as permanent state. Announce a rejected drop or cancellation with meaningful text rather than a success message.

Convert coordinates at the actual rendered size

The board's 300 × 200 space is a drawing coordinate system, not a fixed CSS size. Map pointer coordinates from the actual rendered board into that space, accounting for scale, offsets, and any aspect-ratio padding. Recompute that mapping after a relevant size change.

Do not use a tray coordinate or an unscaled screen coordinate as a board slot. Reject points outside the board and respect the actual slot outline rather than a broad bounding box that includes clipped-away areas. Test the boundary near tabs/sockets as well as the center of a slot.

The game needs these checks at the actual article content width and at 360px. A correct drop on a wide desktop board does not establish that touch coordinates remain aligned after the layout stacks the tray and board.

Give keyboard and tap users the same placement rule

Provide labeled piece selection followed by activation of a labeled slot. Both keyboard and tap selection use the same canonical placement function as dragging. There is no drag-only route to completing the puzzle.

Expose which piece is selected, how many pieces are correctly placed, and whether the latest placement was rejected or committed. Keep focus usable after either result. While a pointer drag is active, prevent a second placement transaction and tell the reader to finish or cancel it first.

Reset should cancel any active drag, clear capture ownership and selection, restore all six tray locations, clear stale completion/error status, and report the new 0-of-6 state. It must not leave a hidden pointerup able to place an old selected piece afterward.

Copy this prompt to specify the native puzzle

Create an original self-contained HTML/CSS/JavaScript/SVG six-piece interlocking-picture puzzle as a native file, with the complete source and a manual test matrix. Use https://developer.mozilla.org/en-US/docs/Web/API/Pointer_events and https://developer.mozilla.org/en-US/docs/Web/SVG/Reference/Element/clipPath for primitives only; do not copy demos or art. Original abstract sun/river/hill SVG, no external image, uploads, network, tracking, localStorage or saved scores.

Normalized board 300×200, three columns/two rows, base cells 100×100. Canonical row-major P01–P06 map only to S01–S06. Flat perimeter and seven internal shared-edge segments: four vertical and three horizontal. Store each segment once; neighboring contours traverse the same path reversed so tabs/sockets complement. Use real interlocking curves, not rectangular tile substitution. Same global SVG picture coordinates for all clipped pieces; tray/drag group transforms must not resize the picture independently per piece. Fixed orientation, no rotations or clusters; preserve tabs in tray. Reference picture and text alternative.

State: unique piece committed location, slot occupant, selected piece, active pointerId, prior committed location and clear transaction phase. One common validated placement function for pointer/keyboard/tap. Only own canonical empty slot commits; correctly placed pieces lock until Reset. P02→S01 reject/restore/count unchanged; P02→S02 commits once. Progress derived from distinct correct IDs: 5/6 incomplete, 6/6 complete only if every mapping correct. Duplicate IDs invalid, never extra count.

One active pointer; ignore other-pointer drag/commit events. Matching pointerup evaluates actual target, commits valid or restores invalid, and clears the transaction before capture cleanup. Lost capture after valid commit must not undo it. Pointercancel/Escape or unexpected capture loss during an active unfinished transaction restores prior location, clears selection/drag and releases owned capture without increasing count. Ignore late events after cleanup/reset. While dragging, block another keyboard/tap transaction with clear feedback.

Derive board coordinates from actual rendered geometry, scale and aspect-ratio padding, not fixed screen pixels. Reject outside-board and clipped-away target regions; respect complementary outline hit tests. Recompute mapping on resize. Keyboard/tap select labeled piece then activate labeled slot using the same rules, with focus, selection and explanatory status feedback. Reset restores all six tray pieces/count 0, clears selection/capture/transaction/stale announcements and reports the new state.

Test matrix: all seven shared seams; picture continuity; wrong P02/S01; valid placement; duplicate/occupied attempts; 5/6 versus 6/6; pointercancel/Escape; unexpected capture loss; lostcapture after commit; second pointer; Reset during drag/after completion; keyboard-only and tap completion; resize and tab/socket hit boundaries. Check actual article width/360px with no page-level overflow. Report performed tests separately from proposed ones. Hosted script behavior, visual seams and assistive results require actual verification; no claim that this prompt created a tested artifact. Native source is authoritative; optional NOS review has no export, automatic update or saved-progress promise.

Keep the native source beside the interaction contract

Maintain the native HTML/JavaScript/SVG source, edge definitions, and actual test results together. A reviewer should be able to trace a rejected placement and a canceled drag without guessing which event changed the progress count.

NOS can save and render HTML. A prepared HTML explanation can present the original puzzle's placement contract and test matrix beside the maintained source. Verify hosted script behavior and every intended interaction in the actual environment before describing a working product example; rendering alone does not establish it.

Explore NOS with the contract first. The useful result is six complementary pieces and a completion state that reflects their committed locations.

Keep reading