37signals recently released a blackboard web app for iPad called Chalk.
The manifest is a nice summary of the contents, and allows browsers to cache the app for offline use. Combine this with mobile Safari's "Add to Home Screen" button and you have yourself a free chalkboard app that works offline.
CACHE MANIFEST / /zepto.min.js /chalk.js /images/background.jpg /images/chalk.png /images/chalk-sprites.png /images/chalk-tile-erase.jpg /images/chalk-tile-red.png /images/chalk-tile-white.png /stylesheets/chalk.css
Not much there, just 10 requests to fetch the whole thing. 11 including the manifest. In we go.
Standard html5 doctype, and a manifest for application caching.
The rest of the HTML is mainly structural. There is not a single text node in the entire tree (excluding whitespace). The chalkboard is a canvas element and an image element used to render the canvas contents as an image for sharing. The other elements are just sprites and buttons. There are div elements for the light switch and shade (a dimmer on each side), share button, instructions on sharing, close button, ledge, chalk, eraser and corresponding indicators. Phew, that was a mouthful. (oblig: "that's what she said!")
Zepto is a tiny, modern JS framework for mobile WebKit browsers such as those found on iPhone and Android handsets. I'm not going to cover it here but I'll mention that it's similar in feel to jQuery. In fact it tries to mimic jQuery very closely to make migrations from Zepto to jQuery easy, and vice versa. The reason it weighs in at just under 6k (2k gzipped) is that it doesn't overreach or have to support legacy crap like IE6. It was started by Thomas Fuchs so you know it's good.
Display (CSS & Images)
6.6k, 385 lines. This is basically half of the text portion, excluding Zepto. There are 6 images including one called chalk-sprites.png. Interesting. Let's look at the background first though.
The background is the blackboard itself, and is almost square at 1024x946. The cork border and light switch are there too. This is set as the background-image of the html element and is positioned at a negative x or y in order to centre it properly. CSS media queries are used to detect the screen's orientation. This way the same image is used for both orientations, clever.
Just a canvas element positioned over the chalkboard using media queries. There's also an image element called "output" used to render an image for sharing.
Sprites are used for all the other elements: ledge, chalk, eraser, tool indicator, share button, instructions, and close button (to leave the sharing mode). Positioned using CSS, standard stuff. There is white text alongside those green arrows. If you want to see it we'll have to change the background to black.
Light Switch & Shade
When you touch the light switch on the left side of the chalkboard - only visible in landscape orientation - the cork border dims and the ledge and share button disappear, leaving the chalkboard under the spotlight all classy like. The shade consists of two "dimmer" div elements inside a shade div, which is hidden by default.
The dimmers background color is black at 67% opacity. The shade element fades in using -webkit-transition: on its visibility property while the dimmers use CSS3 transitions on their background. The dimmers are positioned using media queries as well, one on each side of the board. Interestingly their parent shade has a height and width of 0. Rather than each having a unique id they just have the class "dim" and the :nth-child pseudo-class selector is used to position them independently.
The light switch itself is displayed only in landscape orientation, again using a media query.
There are 2 layers to the tools on the ledge. There are the images of the tools and their indicators, but also an anchor element for each tool that acts as targets to select them. When tools are select the indicators fade in and out using CSS3 transitions on opacity by adding and removing the class "active" on the tool.
There are pattern images for each colour of chalk, and one for the the eraser. The eraser "pattern" is the entire blackboard so erasing it doesn't look ugly. I love that kind of attention to detail.
The shade effect that happens when you hit the share button is similar to the shade effect used for the light switch. It's a bit more complex as the sharing instructions are positioned differently in portrait and landscape orientations, but there's nothing really new in there (that I can see).
The rest of the CSS is largely presentational stuff like removing margins and padding, and positioning using lots of media queries. You can see it all at chalk.37signals.com/stylesheets/chalk.css.
5.5k in about 170 lines. That's just half the size of the CSS.
Sam Stephenson shared the original CoffeeScript source with us. It's about 150 lines, and is a bit easier to read as CS is far cleaner than JS.
The bulk of the magic is done w/ hardware accelerated CSS3 rather than slow JS animation using setInterval and setTimeout to change properties. That sort of thing isn't novel anymore anyway. The fact that JS is really only used for drawing and toggling CSS classes is pretty awesome!
The entire contents of the JS reside inside the DOMContentLoaded event handler attached to window.
First we get a handle on all the elements and the canvas' 2d drawing context. I almost want to say views and controls as it really feels just like hooking up a controller and view in a desktop GUI app. Sometimes the line between dynamic web page and web app are blurred, not so here. Chalk is 100% app.
The canvas' dimensions and pen are initialized in lines 13 - 19, and then the chalkboard background is drawn onto the canvas using the
The canvas offsets are cached for calculations, and are updated when the window fires the "orientationChange" event. Next up tools (a.k.a. pens) are created and initialized.
createPattern(name, callback) loads one of the pattern images, chalk-tile-*, and then creates a pattern in the drawing context and passes it to the given callback.
setStroke(pattern, width) effectively sets the pen used for drawing, described as a pattern & stroke width. The patterns are initialized and the white pen is passed to setStroke since it's the default tool.
The last part defines the 3 tools, note that the active tool "white_chalk" is at the end. Also note that the tool names are the ids of the target elements in the ledge.
activateTool(tool) accepts a tool name. The tool to activate is moved to the end of the tools array on lines 31-32, activeTool is set to the given tool as well on line 32. The reason for moving the active tool to the end of the array is revealed in the for loop on line 34, the order of the tools array determines their z-index ordering (highest number is in front). Then the 'active' CSS class is added to the active tool to show the indicator, and then the pen is set by assigning a pen to the context's
Finally the white_chalk tool is activated and the click event for the tool targets is setup.
Drawing is done by listening for touch events on the canvas element. An array of points to draw is initialized to a 1-element array containing
null. Null values make the draw function break up the line being drawn by skipping the next point in the array. x and y coords are initialized in touchstart, points are appended to the points array in touchmove, and the touchend handler appends two points and null to the points array to end the line. I'm not sure why
[x, y] is used as the points in the touchend handler rather than coords from the event. Please leave a comment if you know why!
The draw function is called for each point in the points array at 30ms intervals. A line is started by calling
context.beginPath(), each point is drawn, and then the line is ended with
The 2nd condition of the while loop ensures that we don't draw for too long, as bad things would happen if the function were executed a 2nd time while it was already running.
Sam Stephenson was kind enough to clarify these points. See his comment below the post for clarification on using [x, y] in the touchend handler and the 10ms limit when drawing points.
Light Switch & Shade
When the light switch is touched (or clicked) the shade class on the body element is toggled. Nothing to it.
The share window is opened after a 10ms delay, just enough time for any drawing to be completed before rendering the image. The image is created by assigning the result of canvas'
toDataURL() method to the output image element's src attribute.
When the share window is closed the output image element gets its src set to the sprites image.
I'm not sure why that was done. As Sam mentions in his comment below, this is done to reclaim the memory used by the rendered image.
The rest of the code there just sets up event handlers and toggles CSS classes.
That about covers it. Don't have an iPad? Play around with it anyway, but be warned that you can't draw anything. You can select chalk and the eraser and hit the light switch. I instinctively tried touching my MacBook's display but alas it doesn't magically respond to touches, lame.
Have fun drawing. Thanks to 37signals for a beautiful (and useful) example of a few modern web technologies.