Monthly Archives: April 2011

How Is XKCD In 3D?

So today is April Fool’s day and all the xkcd comics are now in 3D!

XK3D for N64
It’s like xkcd64 or something.

When I first visited xkcd this morning, I was immediately temped to use the F word – Flash. A little right-clicking though revealed that this was not the case, which led me to the the golden question of the day – how?

What xkcd did today is an incredible demonstration of how a little knowledge of CSS can go a long, long way.

The comics are actually several PNG files layered on top of each other using absolute positioning:

<div id="comic">
    <!-- style attributes omitted -->
    <img src="http://imgs.xkcd.com/xk3d/880/headache_box.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_handle.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_corners.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_corners.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_bike.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_corners.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_handle.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_girl.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_corners.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_corners.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_corners.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_corners.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_corners.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_desk.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_mask.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_corners.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_computer.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_desk_corners.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_guy.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_corners.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_computer.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_corners.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_desk.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_box.png" >
    <img src="http://imgs.xkcd.com/xk3d/880/headache_text.png" >
</div>

If you want to see all those images individually, see the bottom of this post.

In the HTML for xkcd you can find a <style> element which is applying relative positioning to #comic:

<style>
    #comic { position:relative; display:inline-block; margin:10px; }
</style>

… since #comic is using relative positioning, as opposed to the default static positioning, any absolutely positioned elements inside <div id="comic"> are positioned with respect to <div id="comic">. By manipulating all the images/layers individually it creates a 3D-looking effect.

Try This at Home

If you want to skip the trying part and just see the result, I posted it online.

For kicks, try this: take the following 2 images…

A square box with transparency in the middle
and
Corner dots and more transparency

… and put ‘em together in an HTML document like so:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <title>How Is xkcd In 3D?</title>
    <style type="text/css">
        #comic {
            position:relative;
            top:0px;
            left:0px;
            margin:auto;
            height:183px;
            width:202px;
        }
        #comic img {
            position:absolute;
            top:0px;
            left:0px;
        }
    </style>
</head>
<body>
    <h1>How Is xkcd In 3D?</h1>
    <div id="comic">
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/box.png"
             alt="A layer in my 3D image" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/box.png"
             alt="A layer in my 3D image" />
    </div>
</body>
</html>

Each of these layers is supposed to appear further away than the last, so use some style attributes along with the CSS width property to make them appear smaller: (see one point perspective on wikipedia)

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <title>How Is xkcd In 3D?</title>
    <style type="text/css">
        #comic {
            position:relative;
            top:0px;
            left:0px;
            margin:auto;
            height:183px;
            width:202px;
        }
        #comic img {
            position:absolute;
            top:0px;
            left:0px;
        }
    </style>
</head>
<body>
    <h1>How Is xkcd In 3D?</h1>
    <div id="comic">
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/box.png"
             alt="A layer in my 3D image"
             style="width:200px" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:195px" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:190px" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:185px" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:180px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:175px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:170px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:165px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:160px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/box.png"
             alt="A layer in my 3D image"
             style="width:155px;" />
    </div>
</body>
</html>

… and then also offset the position of each image using the top an left CSS properties:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <title>How Is xkcd In 3D?</title>
    <style type="text/css">
        #comic {
            position:relative;
            top:0px;
            left:0px;
            margin:auto;
            height:183px;
            width:202px;
        }
        #comic img {
            position:absolute;
            top:0px;
            left:0px;
        }
    </style>
</head>
<body>
    <h1>How Is xkcd In 3D?</h1>
    <div id="comic">
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/box.png"
             alt="A layer in my 3D image"
             style="width:200px;top:0px;left:0px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:195px;top:2px;left:2px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:190px;top:5px;left:5px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:185px;top:8px;left:8px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:180px;top:10px;left:10px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:175px;top:12px;left:12px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:170px;top:15px;left:15px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:165px;top:18px;left:18px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:160px;top:20px;left:20px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/box.png"
             alt="A layer in my 3D image"
             style="width:155px;top:22px;left:22px;" />
    </div>
</body>
</html>

The result should be similar to 1 point perspective:

One point perspective

Now, add some JavaScript: use jQuery’s mousemove function to track the movement of the mouse:

    <script type="text/javascript"
            src="//ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js">
    </script>
    <script type="text/javascript">
        $(document).ready(function() {
            $('body').mousemove(function(event) {
                var msg = "How Is xkcd In 3D? "+
                                          "(" + event.pageX + ", " + event.pageY + ")";
                $('h1').html(msg);
            });
        });
    </script>

Generally speaking, the images which are "further" need to be offset more as the mouse is moved further from the center of the box. The following uses a fairly simply equation to achieve that effect:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
    <title>How Is xkcd In 3D?</title>
    <style type="text/css">
        html, body {
            height:100%;
        }
        #comic {
            position:relative;
            top:0px;
            left:0px;
            margin:auto;
            height:183px;
            width:202px;
        }
        #comic img {
            position:absolute;
            top:0px;
            left:0px;
        }
    </style>
    <script type="text/javascript"
            src="//ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js">
    </script>
    <script type="text/javascript">
        $(document).ready(function() {
            $('body').mousemove(function(event) {
                var x = event.pageX;
                var y = event.pageY;
                var msg = 'How Is xkcd In 3D? (' + x + ', ' + y + ')';
                $('h1').html(msg);
                var i=0;
                $("#comic img").each(function() {
                    $(this).css("top", ((0-y)*i)/100+"px");
                    $(this).css("left", ((0-x)*i)/100+"px");
                    i++;
                });
            });
        });
    </script>
</head>
<body>
    <h1>How Is xkcd In 3D?</h1>
    <div id="comic">
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/box.png"
             alt="A layer in my 3D image"
             style="width:200px;top:0px;left:0px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:195px;top:2px;left:2px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:190px;top:5px;left:5px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:185px;top:8px;left:8px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:180px;top:10px;left:10px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:175px;top:12px;left:12px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:170px;top:15px;left:15px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:165px;top:18px;left:18px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/corners.png"
             alt="A layer in my 3D image"
             style="width:160px;top:20px;left:20px;" />
        <img src="http://richard.jp.leguen.ca/images/howIsXkcd3d/box.png"
             alt="A layer in my 3D image"
             style="width:155px;top:22px;left:22px;" />
    </div>
</body>
</html>

Give it a try! Mind you, xkcd is using some significantly more interesting math (parallaxes and stuff like that…I don’t know anything about paralaxes which doesn’t involve the destruction of Coast City…)

All The Images From xkcd880:

For those interested, here are the layers from today’s comic (01/04/2011) which has 25 layers – some of them are the same image repeated, and some of them are re-sized using CSS: (all these images are from xkcd.com)

A layer from the 3D comic
These ‘box’ around the web comic.
A layer from the 3D comic
The handle to the girl’s (Megan?) bike
A layer from the 3D comic
The corners: these ‘corners’ are repeated, just like in the above example I wrote.
A layer from the 3D comic
The girl’s (Megan?) bicycle.
A layer from the 3D comic
The girl’s (Megan?) herself.
A layer from the 3D comic
The desk
A layer from the 3D comic
I have no idea what this one is for… it’s empty.
A layer from the 3D comic
The guy’s (Cueball?) computer.
A layer from the 3D comic
The desk corners, which move independant of the desk
A layer from the 3D comic
The guy (Cueball?) himself
A layer from the 3D comic
The webcomic text