Jigsawr (2): Design

I created an SVG-powered jigsaw app for the 10K Apart contest. While the judges ponder their decision, just for fun I have created it its own web site, jigsawr.org.

In a previous entry I outlined Minification of the JavaScript and CSS

Design Features

To fit in 10K I had to be fairly ruthless as to which features were absolutely necessary and which merely nice-to-have. The main ones are:

  • Given a search term, find an image URL;
  • Given an image URL, display puzzle pieces;
  • Large pictures are scaled down and small ones scaled up to fit in the arena;
  • Pieces are moved with drag and drop;
  • Pieces snap together when brought together to make hunks;
  • Hunks of puzzle get dragged around as a unit.

I didn’t want the pieces to snap in to their position on the board. The effect I wanted was like when my mum worked on jigsaws on a big board, assembling hunks and then combining them.

Some nice-to-have features would be:

  • Vary the size of the bumps to make the pieces’ shapes more variable;
  • Pieces that rotate;
  • Drop shadow for hunks which is deeper for the one being dragged (to give faux 3D);
  • Sound when snapping pieces together;
  • Message when puzzle complete;
  • Permalinks for sharing a particular jigsaw with your chums.

I ran out of space or time for these, but I might add them later on anyway.

Jigsaw Pieces in SVG

Since I wanted curvy shapes, SVG seemed the logical way to implement the puzzle itself.

I wanted to do this without manipulating the image directly—each puzzle piece displays a piece-shaped portion of the original image. I was using SVG for this, so there are three ways that occurred to me:

  1. Each puzzle piece has a reference to the image with a different clipPath element.

  2. Each puzzle piece is a path element whose fill refers to a pattern that references the image.

  3. Each puzzle piece is a path element with a filter that uses the image as a raster source and then offsets it.

The problem with using image and clipPath (at least in the way I tried it) was that Safari treats drag-and-drop specially for images, allowing you to drag images out of a page on to your desktop. The effect of this was that when you dragged a puzzle piece, a ghost of the complete image would appear and it would go awry. It is possible that there was a way to fix this, but in the end I decided to try a different approach instead.

Option 2 is what I got working in the end. Each piece is a path whose fill references a pattern whose sole content is a use element referencing a symbol which contains the image. This means the image should be loaded only once (not once per piece), though I do not know whether the buffering of the pattern image will consume graphics memory based on the entire image size or just the fraction visible in the puzzle piece.

The paths for the pieces are generated by JavaScript, using a hash function of the row and column of the piece to decide whether its bump pokes out or in. As is common in graphics work, the hBump and vBump functions are very similar since they just swap x coordinates for y coordinates, but it isn’t quite possible to write one cleverer function that works either way. When every character counts towards the 10K total, the almost-duplicated code is annoying.

Drag & Drop

The trickiest part of the JavaScript programming was getting the drag-and-drop interaction to work. It is particularly exasperating to debug since you can’t interact with the debugger without interrupting the mouse movements you are trying to observe. I had to add a logging system to spurt out progress messages.

Eventually I settled on a system where, on mousedown, I record the clientX and clientY attributes of the event, and the start X and Y of the puzzle pieces in the affected hunk. Then as mousemove events arrive, the change in event coordinates is added to the the piece coordinates. This avoids having to translate from the coordinate system used for events and the coordinate system used for the pieces.

The other thing I had to do was ensure that the mousemove events are captured by the container (g element) containing all the pieces, so that they are still caught even if the user’s mouse movement takes it out of the boundary of the piece. For this to work, the background (the big grey rectangle) has to be included in the enclosing g element, since otherwise the container is transparent where there are no pieces and does not receive events.

It is possible that sufficiently clever use of the properties controlling movement events would have allowed for this to work with transparent background, but once I had got something together I was not in the mood to fiddle with it.

Host Page

While the jigsaw itself is purely SVG + JavaScript, it is supplied with its image URL, image dimensions, and number of pieces by the HTML page it is embedded in. (I did not attempt to merge the SVG in to the HTML page, but used an embed tag. I am assuming without checking that embed is permissible HTML5, given it is the only way to embed SVG that works reliably.) This page takes care of displaying the form, loading the image to get its dimensions, if necessary, and embedding the jigsaw.

The Flickr support works through the Flickr API. Using the JSONP approach—where my app performs a simple HTTP request and Flickr returns the data as an object in JSON format—is a snap with jQuery. I created a flickrCall function to do this not so much because it was complicated as to save a few dozen bytes’ repeated code.

By the way, Flickr enforces ‘safe’ search, so feel free to create jigsaws of big cocks and great tits.

The first version of the app had two forms, not one. The Flickr tag form had a button labelled ‘Next >’ which did the Flickr API call and copied the URL in to the second form, which had the ‘Jigsaw!’ button. After complaints from my play testers, I did the typical thing, which is ponder adding messages or arrows to tell people the correct order to operate the controls. Then I realized the correct solution was to mash the forms together in to one. The new form has both URL and tag fields, but only one is shown at a time. I had this brainstorm just before going to bed, but it only took half an hour or so to refactor the jQuery code to implement the new user experience.

Future

The app has been entered in to the contest and awaits the judges’ verdict. Meanwhile I have its own little web site, http://jigsawr.org/

I should add a link on the jigsaw to itself, so you can share your favourite jigsaws with your friends.

It should be easy to create a bookmarklet that examines the current page, finds the largest image on it, and displays a jigsaw of that image.

I am considering how I might implement rotating of pieces—though most players will agree it is more likely to be annoying than fun.

One thing I have not worked out how to do: an iPad-compatible version. Drag & drop does not work because dragging is used for moving the page as whole!