I put together this
demo quickly last summer to experiment with CSS 2D Transforms as they were
being implemented in Webkit and Firefox. (We’ll see about 3D transforms
soon, BTW.) Since I have always liked the idea of poetry magnets, this seemed
like an ideal little project to get started with those. Here is what it looks
like now, as seen by Safari on Mac OS 10.6:

Scattering the magnets
The implementation is very simple: a magnet is a just a div
element on the document fridge door. We just need a little bit of
style such as a border, a soft shadow, and some colors, rounded
corners of course, and here we have our first magnet:
anathema
At first, I wanted to do as much as possible with transformations. So I just
threw in a bunch of magnets in a parent div and set a CSS
transform property with a random translation and rotation to
scatter the magnets everywhere. Which was fine... except that if for some
reason the original position of a magnet changed (for instance when resizing
the browser window) then the translated position would also change in a
sometimes unpredictable way. It was also difficult to keep track of the magnets
when letting the user move them around, so I decided to stick with the
old-school approach of absolute positioning for the magnets, and using the
transform property for rotations only.
In the end, magnets share the same magnet class,
with some additional classes for custom styles. The position (top,
left, and z-index properties) and rotation
(transform property with a rotate(θ deg)
value) are set programmatically through Javascript so that the user can drag
them around the fridge door surface. Dragging can either change the position or
the rotation of the magnet; we catch the mousedown events on
magnets and decide to change the position if the pointer close to the center of
the magnet, or change the rotation if the pointer was closer to the
corners.
A note about browser compatibility: since transforms are
pretty new, both Webkit and Firefox implement the property with their own
names, which means that we have to set both -webkit-transform and
-moz-transform for every element to work across both browsers and
their derivative, such as Safari and Chrome for Webkit. (Opera 10.50 implements
their own -o-transform attribute, so we throw that into the mix as
well; and the less said about IE, the better.) Fortunately, they all seem to
agree on the syntax and semantics of the values. So let’s see our above magnet
again, this time translated and rotated, using the translate CSS transform now
because I do want to translate it relative to its normal position:
anathema
Finally, I know that I should say something about z-index,
which was a bit tricky to make work correctly across different browsers, but I
forget what the trick was. Also I’m still not sure that I understand how
z-index actually works (or most of CSS, for that
matter.)
Generating magnets
Now we need to generate sets of words to have interesting magnets to play
with. I started by downloading Shakespeare’s sonnets and Walt Whitman’s Leaves of Grass from
Project Gutenberg, fed these to a very naive tokenizer written in Perl and
extracted all words that had more occurrences than a given threshold (I forget
what the threshold was set to, but something quite low.) I threw in Perl’s
keywords and operators and a bunch of Unicode symbols for good measure, did a
little bit of editing, and generated hundreds of poetry magnets with little
effort.
In the future I will try and add different languages; the
problem here is that the very naive approach taken here works well for English,
but shows its limit very quickly when moving to other languages. There are two
main issues: first, we need a real morphological analyzer for other languages,
and not such a quick and dirty, regular-expression based tokenizer. This is not
so bad because we can find good quality, open-source tools for this job. The
second issue is a bit trickier as we need to worry about conjugation, plural,
gender, agreement, &c. so this part requires some experimentation.
Another source of words to play with is not famous poets, but
anonymous ones: the users themselves. Since we don’t deal with actual magnets
but virtual ones, we can make them editable. This is as simple as setting the
contentEditable property of the magnet’s div element
to true when a blank magnet is created, and setting it back to
false when done (i.e., when the return key is pressed.)
The only issue left was to deal with positioning of a magnet that can change
size, because transforms are relative to the center of the magnets. So every
time a key is pressed, while the magnet is editable, we check for its new size
and change to position so that its center does not move. This is where
getBoundingClientRect() comes in very handy, by the way. (And this
needs to be fixed for Opera, actually.)
localStorage to the rescue
Another nifty new feature that I wanted to experiment with was the new
storage features of HTML5, i.e., Web Storage. At first I tried the
client-side
database API, which worked well in Webkit but not in other browsers (even
Chrome, although based on Webkit, didn’t implement any storage API at that
point) and in retrospect seems like a misguided effort to specify SQLite as part of HTML5. (Nothing against SQLite,
which is a great piece of software.)
So I switched to the window.localStorage
interface where I simply stored some JSON strings with the position of every
magnet. It seems to be consistently implemented all-around (it took some time
for Chrome to catch up though) and allowed the user’s fridge door to become
persistent. As soon as a magnet is moved or rotated, everything gets saved and
is ready to be restored by the time the next session starts.
The last piece of the puzzle was to implement a little server
back-end to allow users to share their poetry. Because the browser’s local
storage is not accessible to the outside world, we need to save the data on
some shared resource to make it accessible to others. By associating a unique
identifier to every saved session, they can now be easily shared by URLs, such
as the one in the
screenshot above.
Dragging again
Another new HTML5 feature that I experimented on during development was
HTML5 drag and
drop. In the end it was not a very good fit for my use case and I ended up
dropping it, if you will, although it will make a come back in a future post
about a different application.
Implementing dragging of the magnets was not too bad,
although handling rotation added a bit of complexity (see above.) Dropping was
another matter, because I wanted to be able to drop magnets over the trash can
as a way to discard them. I ended up using the elementFromPoint
method to check whether my drop-off point was the trash can element; but in
order for that to work, the magnet that was being dragged had to be momentarily
hidden, by setting its visibility style property to
hidden, so that it wouldn’t interfere with
elementFromPoint. I even threw in some sounds for the trash can,
but I’ll write in more details about the audio element very
soon.
That’s all for now; next time we’ll see some more usages of
CSS 2D transforms but this time we’ll throw in some video as well.