Jigsawr (2): Design
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:
Each puzzle piece has a reference to the image with a different
Each puzzle piece is a
pathelement whose fill refers to a
patternthat references the image.
Each puzzle piece is a
pathelement with a filter that uses the image as a raster source and then offsets it.
The problem with using
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
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
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.
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
functions are very similar since they just swap
x coordinates for
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
Eventually I settled on a system
mousedown, I record the
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
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.
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.
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!