Thursday, December 22, 2011

JavaScript secrets of Bret Victor's homepage

I recently came across the site worrydream.com, which implements an amazing navigation experience through JavaScript and HTML5. The page displays dozens of page icons arranged into angled strips that fit the page, and when you click on one, the icons fly around the page while the clicked link slides in at the bottom. It's an amazing effect - if you haven't seen the site, click on the image below to try it. (Otherwise this article won't make much sense.)

Looking at the page, I couldn't figure out how CSS and JavaScript could perform the effects on the page - the way the icons moved around, the angles of the icons and the page, or the way the page blurred and appeared. Using Inspect Element in the browser showed a whole bunch of complex divs, but didn't give much clue as to how it works.

I set out to understand in detail how the page works. In the process, I learned a lot of interesting JavaScript and CSS techniques, and I'll share them with you in this article.

So how does the page work?

If you do "View Source" on worrydream.com, you may be surprised - the page is mostly just a bunch of lists of text and links. In fact, if you use Internet Explorer, that's all you'll see - jast a 1990's-era list of links. The following snippet shows part of the HTML source, which consists simply of a list of text and links. The HTML doesn't even contain images. The horizontal strips of icons aren't implemented anywhere in the source. So where does the page content come from?

<h2>Dynamic Pictures</h2>
<ul>
<li class="pageWidth-1070 subtitleOnly-1"><a id="DynamicPicturesMotivation" href="http://worrydream.com/DynamicPicturesMotivation/">
<span class="title">Dynamic Pictures</span></a>:
<span class="subtitle">Why do we make pictures by writing code? How can artists draw data graphics, visual interfaces, and other
<b>pictures that change</b>?</span>
</li>
<li class="pageWidth-1150 noPreload-1"><a id="MagicInk" href="http://worrydream.com/MagicInk/>
<span class="title">Magic Ink</span></a>:
<span class="subtitle">A treatise on a new approach to UI design. Interaction considered harmful.</span>
</li>
<li class="pageWidth-920"><a id="Substroke" href="http://worrydream.com/substroke/">
<span class="title">Substroke</span></a>:
<span class="subtitle">Sketch of a dynamic drawing language.</span>
</li>
</ul>

Generating totally new content from the existing page

It turns out that the JavaScript entirely hides the existing page content (by setting display: none on the top-level div) and dynamically generates totally new page content, which is what you see.

The following image shows the part of the page that is generated by the above HTML snippet. You can see a clear mapping from the headings and links in the source above to the text and icons in the image below, but this is all dynamically synthesized. That is, the JavaScript goes through the existing page and for each heading, list item, link, etc, it generates a bunch of entirely new elements. The source above is not rendered at all.

Note that the text in the HTML is displayed below the icons. In addition, the images are not explicitly specified in the HTML, but come from the id attributes, which I explain in more detail below. I also explain later how the class attributes work.

Most web pages use CSS to style the HTML content of the page with the desired page formatting and layout. Worrydream.com uses a very different approach where the existing page contents is input used to generate entirely new page content. This is one of the most interesting techniques of worrydream.com. It's even more impressive that the "template" content renders nicely as a fallback mechanism for Internet Explorer and other unsupported browsers.

The page implements many different JavaScript objects

The site is implemented with thousands of lines of JavaScript, and the scripts can be viewed here. The page is implemented from many different sub-components, each with complex behavior. A quick overview of these objects will help explain how the page is created.

The Site object is the key object that does most of the work. The main logic in Site scans through the divs in the original document and creates a SiteSection and SiteSectionTitleSet for each div. SiteSection in turn extracts the h2 and ul tags from the original page, creates a SiteStripSegment for each one, and lays them out on the page into a collection of SiteStrips. The Site object also creates many relatively minor components: SiteBackground for the page background, SiteContactSet for the sharing links, SiteDoodle for thie images at the top, SiteHomeButton, shadows, SitePageArrowRegion for the left and right buttons, and the custom scrollbars SiteXScroller and SiteYScroller.

The page format explodes dramatically when an icon is clicked

When an icon is clicked, the whole format of the page changes and the page components fly around in a dramatic way, seemingly exploding randomly. It is hard at first to follow what happens to all the icons when the page rearranges. In fact, the behavior is simpler than it seems. The new icon layout consists of a very long linear layout at the top of the screen, with most of the icons either off the left or the right of the screen. If you watch carefully, you can see the icons move into their new positions. Clicking the SiteHomeButton in the upper left reverses this movement. The diagram below shows that many new objects are used to display the page. The most important change is that the ContentContainer displays the page corresponding to the clicked icon.

The SitePageSet becomes visible after an icon is clicked. The SitePageSet manages the blurred page images, most of which extend off to the left and right of the visible screen. To initialize it, SiteSection added a SitePage to the SitePageSet for each element that it processed.

The ContentContainer holds the actual page contents when an icon is clicked. When I first looked at the site, I figured "Oh, the page you click on is loaded into an iframe." It turns out the site is considerably more complex. Simple pages are loaded into an iframe but there is special logic do display Vimeo, movies, images, or embedded HTML.

Pre-computed thumbnails and page images

One of the most dramatic elements of the page occurs when you click an icon - a blurred version of the page slides in and then jumps into focus. You might wonder what CSS trick creates the blurred page. The blurring turns out to be entirely precomputed - there is a small blurred snapshot of each page in a page image directory and that snapshot simply gets displayed while the real page is loading.

Similarly, all the page icons are stored in a thumbnail directory. Interestingly, the page images and icon images do not explicitly appear in the source, but are dynamically created. The id attribute on each link is used to generate the URLs. For instance a link with id="ScrollTabs" corresponds to images named ScrollTabs.jpg in the PageImages and ThumbnailImages directories.

The two lessons are: first, sometimes it's better to use an offline brute-force solution such as precomputing blurred images for every page, rather than trying to do it dynamically. And second, you can use a naming system to generate image links, rather than hardcoding them.

Details and techniques used by the site

The above explanation covers the high-level components of worrydream.com. The remainder will describe some of the interesting low-level functionality, implementation, and CSS tricks.

BVLayer library

The worrydream.com site is based on a "layer" abstraction, which is implemented through the BVLayer library. Layers can be considered as displayable objects implemented in JavaScript, with complex logic to control their appearance and behavior. Layers form a hierarchy, with layers containing other layers. If you look at the above diagrams, the components (SiteSection, SiteStripSegment, etc) are layers.

Each layer is implemented as a div, normally with another BVLayer div as a parent. This explains why looking at the page structure with Inspect Element just shows a huge number of divs.

Most of the JavaScript objects in worrydream.com are subclasses of BVLayer, with additional functionality implemented in the subclass. For some objects, this additional functionality is just a background image or an event handler, while other objects may be extremely complex, for instance Site, which hold the top-level logic for the page, or SiteSection, which has the layout engine for the page.

The library is complex - almost 1000 lines of JavaScript, and provides many functions. It provides a system to transform layers by moving or rotating them, as well as implementing animation. It includes a framework to handle mouse and touchpad events and provides browser-independent abstractions.

The touch events API

Many browsers now support a touch events API for use with touch-screen devices. These events are similar to the old mouse events such as mousedown, mouseup, but are modified to handle touch-screen characteristics such as multi-touch and pressure. The specific events are touchstart, touchend, touchenter, touchleave, and touchcancel. These events allow JavaScript applications to work in a natural way with touch-screen devices.

Worrydream.com makes heavy use of touch events. The BVLayer library builds an event system on top of mouse and touch events to detect movements, taps, double taps, and to implement touchable regions. This allows the site to support complex interations both on desktop and touch devices.

Web fonts

Much of the character of worrydream.com comes from its unusual display font. The website uses Komika fonts from FontSquirrel. These fonts are used through the CSS3 @font-face feature.

The web fonts feature allows a website to download desired fonts, rather than being limited to the standard browser fonts. A simple explanation of @font-face is here or you can read the official W3C CSS Fonts document. Web fonts can be obtained from a variety of sources, such as Google web fonts, Typekit, or Fonts.com.

MooTools

The worrydream.com site is implemented using MooTools, which is apparently the second-most popular JavaScript framework/library after jQuery. The main distinguishing feature of MooTools is it provides a standard Object-Oriented class model with inheritance, rather than the prototype-based inheritance of JavaScript. MooTools also provides browser-independent JavaScript tools for accessing the DOM, handling events, performing animations, Ajax operations, and other standard JavaScript library features.

The worrydream.com website makes heavy use of MooTools, but builds complex layers of abstraction on top of it.

More information on MooTools is available at MooTools.net, Wikipedia, the MooTorial tutorial site, or books such as MooTools 1.2 Beginner's Guide.

Object parameters come from the CSS class names

If you look at the CSS classes in the HTML source, you see interesting class names such as
<li class="pageColor-0c0c0c pageWidth-900 pageHeight-970 injectContent-1">
Surely there's not a separate CSS class defined for each page color, witdth, and height?

The worrydream.com code uses an interesting properties system (see Site.mergePropertiesFromElement) to turn the "classnames" into object properties. This function parses each hyphen-separated entry in the class attribute, so the above would generate the properties {pageColor: "0c0c0c", pageWidth: 900, pageHeight: 970, injectContent: 1}. These properties are then used to control the new elements that get created on the page.

The properties system provides a few additional features. For instance, it supports string, integer, and percent types - scalePercent-68 turns into {scale: 0.68}. It also allows properties to be inherited from other elements.

This class-based property system is a clever way to pass arbitrary parameters into the page-generation system, while causing these parameters to be ignored by browsers that are rendering the original page. There are over 30 different properties used to change the rendering style for particular sections (filmEdges-1 to give the top strip film-style edges), specify special content (vimeo-23839605 to load a Vimeo video), provide special behavior (magicSubtitle-1 to enable the Easter Egg), specify dimensions (pageWidth-960), and many other functions.

Special support for Ajax URLs

You may notice that as you click on different icons, the URL changes to something like http://worrydream.com/#!/KillMath. Why the strange #! in the URL?

This style of URL is a standard technique for dynamic pages, allowing the use of the back arrow, bookmarks, and sharing links. Normally, if you change a page's URL via JavaScript, the entire page will reload, which is generally undesirable for a dynamic site. However, everything after the pound sign is a URL fragment identifier, which can be modified without reloads. Dynamic pages take advantage of this - they can update the fragment identifier in the URL to reflect page state, without triggering a disruptive page load. The second aspect of fragments in dynamic pages is if the anchor is changed (either by the user including the anchor in a URL or by the back arrow), the JavaScript must update the page to display the "right" content for that anchor.

But what is the exclamation point doing in there? This enables web crawlers to crawl the content, using an Ajax crawling standard. This allows Web crawlers to get the HTML page contents without needing to execute the JavaScript.

This Ajax crawling technique is important for any site that dynamically generates pages with JavaScript and wants the pages to get rendered properly by web crawlers.

CSS transformations

One of the most eye-catching effects on worrydream.com is that most of the elements on the page are arranged at slight angles, rather than the normal grid. This is implemented through CSS transformations, and the BVLayer library provides suport for these operations on any of the layers.

Most modern browsers support 2D transforms through CSS, allowing an element to translate(), rotate(), scale(), skew(), or be transformed through an arbitrary matrix(). (spec) This can be done easily in CSS, for example:

-webkit-transform: rotate(5deg);
Inconveniently, "webkit" must be replaced by "ms", "o", or "moz", depending on the browser type. The BVLayer library provides a browser-independence layer that hides that complication.

Animation through CSS transitions

The most eye-catching part of the site is how parts of the page fly around. This is implemented through CSS transitions and animations. CSS animations are supported by many browsers, and provide an easy way to animate to perform various animations. (spec)

Transitions can be implemented by setting a duration:

-webkit-transition-duration: 1s;
The BVLibrary handles multiple browsers, abstracting out the browser-specific prefixes such as -webkit- or -ms-, and providing fallbacks for less capable browsers. For instance, if the browser doesn't support transitions, the library uses the MooTools Fx.Tween method to perform the animations.

Hardware Acceleration

One trick I learned from the code is that the translate3D() CSS property will enable hardware acceleration on iOS. This lets the site work more smoothly on these devices.

Attention to detail

One surprising thing about worrydream.com is the attention to detail. Whenever I think something has an obvious implementation, it turns out to have additional complexity. For instance, the cannon and windmill images at the top of the page are not just images, but two SiteDoodle classes, which contain animation logic and fade-out logic that is activated when the page changes.

Another hidden feature is the "Easter Egg" that is activated when clicking on the "purveyor of impossible dreams" subtitle at the top of the page. Its implementation is a significant amount of code, but I'll leave the details as a surprise.

The scrollbars at the top and right of the page are not standard browser scrollbars, but custom-implemented scrollbars with their own styling and logic.

The Twitter, RSS, and email icons at the top of the page are not simply icons, but SiteContactSet and SiteContact classes with their own logic, as well as separate implementations for the bottom of the page.

The page background is not just a simple background, but a set of SiteBackground classes implemented from BVLayer. The shadows around the edges of the page are also implemented through BVLayer.

The page contains complex logic to lay out the icons according to the page dimensions and redo the layout if the browser is resized.

My conclusion is that a site like worrydream.com isn't made by simply adding some JavaScript functions to a page, but by implementing every aspect of it with careful attention to the details. I hate to imagine how much time it must have taken to implement the site.

Conclusions

I should re-emphasize that worrydream.com is not my site and I have no connection to it. I found it fascinating and asked its creator Bret Victor if I could study it and write about it. The site has many other pages that display interesting JavaScript techniques and are worthy of investigation, such as Scientific Computation, Ladder of Abstraction, Ten Brighter Ideas, and Explorable Explanations, but I don't have space to describe them here.

By examining the worrydream.com site in detail, I learned a lot about how to build a complex site out of JavaScript and take advantage of CSS3 functions. I hope that you have also learned some interesting techniques by reading this article.

11 comments:

Anonymous said...

I recently came across the site worrydream.com, which implements an amazing navigation experience through JavaScript and HTML5

scrolling with page-up and page-down does not work... arrow keys does not work too... smooth scrolling? where is the amazing stuff you are talking about?

Anonymous said...

I can't see anything good on that website. The UX is awful.

It's slow, it's heavy....

It's a NO NO, for me.

Thanks

Anonymous said...

But innovative nonetheless!

Christian Ehrhardt said...

i think the site is pretty cool, and the fact that he made it in 2011 is even more impressive

fadzlan said...

@Anonymous site seems pretty fast to me.

And it make sense, since the purpose of doing all that is to better present his ideas, with I suppose a bit hard with just static pages.

lanstonpeng said...

The rendering content part is interesting

Fanboy said...

scroll by clicking and dragging!

Peter Müller said...

He managed to break pretty much every way I normally navigate a website.

Scrollwheel? nope
Keyboard up/down? nope
Page up/down? nope
Tabbing through links? nope

I don't see why those things have to be broken to achieve the effect on the page.

Donald Pipowitch said...

No thanks, to much overload!

Anonymous said...

the amount of "hry you kids, get off my lawn" type comments here are, unfortunately typical, and still disheartening.

Why should we stand still on interface design? I had no trouble navigating.

Extensive use of touch events is a hint.

I loved this site and it's content. I am so bored of every single site being 95% the same as every other.

Gary said...

It's not your run-of-the-mill website. If you had taken the time to read through his whitepapers and ideas on his website, you would understand the thinking and visualization that went into creating the website as well.

I personally enjoyed this new experience, bringing a fresh breath to UX and interaction.