Origin

This animation was the first part of a feature called "something virtual that matters". It started mid 2012 while watching a gameplay video of Battlefield 3. I was amazed by all the work that goes into making a video game as realistic as possible. Yet, "it's just a game", as the popular wisdom goes. How can we make something virtual that is more than a game? Can we bring virtual worlds to the status of common reality and have them matter as much?

I am obsessed with these questions, and I wanted to examine what it takes to build a virtual world that matters.

This animation was the first step of an argumentation. It's an illustration of what we can currently have running on a 2D screen. The animation was supposed to go further, with the user being able to steer the plane and make it crash against the canyon, just like what we can do in video games: explosion composition

But, the argumentation goes, no matter how realistic the graphics can get, it's still a partial representation made with visual effects tricks. Let's examine more closely what is happening and go from there: the plane is a 2D projection of a 3D model, the animation is a sequence of 2D images projected across time, and my physical computer is ticking billions of times a second to run all this. Should we see the plane crash into the canyon, no actual plane or pilot would be harmed in the process. We are getting some of the reality (the graphics) but not all of it. What do we need exactly? iMATTER

As a result, I got sucked into a whirlwind of hundreds of more or less relevant questions and observations. For example: we won't believe a plane has actually crashed, but if we replace this animation with a chess game against the computer, we would really believe that we lost or won a game of chess, even if the game is only virtual with no physical checkerboard and pieces. Another observation: the plane might not have really crashed, but my computer running the animation has really got a little warmer. There are physical deposits of the virtual world, like power consumption and heat generation1. Landauer's principle

I decided that the subject is either too huge, or ill-defined, or both. Typical of philosophy.

4 years and a half later, I reopened the animation, decided to clean it, stick a song on it, and call it a day. Here is how!

Graphics
cloud 0 cloud 1 cloud 2 cloud 3 cloud 4 landscape canyon cessna 172 bonzais firs firs
◀ HideReveal ▶

There is the sky, and then there are eleven other layers, each one being a 3D element modeled and rendered in Cinema 4D with an orthographic projection.

The Sky. The sky is a composite of multiple gradients and photo references blended in Photoshop. I didn't have any specific recipe for the composite. I played with photos, gradients and blending modes until I got a result I liked.
sky layers
The Clouds. The clouds are generated in Cinema 4D using particle emitters and Pyrocluster.
clouds particle generation
The Landscape. The background landscape is made of a series of multiple 3D landscapes generated with the native landscape object in Cinema 4D. The landscapes were grouped together and the camera put far away close to the ground to get a wide panoramic view.
landscape 3d model
The Canyon2. The canyon is made by extruding a plane mesh using a displace map.
canyon 3d construction
The trees. The trees are 3D models rendered in Cinema 4D then cloned and blurred in Photoshop, to give the effect of a fast moving object.
trees composition
The Plane3. The plane is an online 3D model of the Cessna 172 Skyhawk.
I slightly edited the model to remove some hard angles from the vertical stabilizer, and to rig the ailerons for the roll animation. The texture map was included with the model. I edited it to customize the lettering on the plane.
cessna 172 3d model
Animation

Two technics were used to animate the scene: 1) 3D animation to get the plane roll motion, and 2) CSS animations to implement all the moving parts on the web browser.

The plane. The roll motion was made in Cinema 4D. I spent some time synchronizing the body roll with the ailerons activation. The animation was then rendered into a sequence of 216 frames, patched together vertically using SpriteGenerator4 to make a spritesheet. Click the sample below to see the full spritesheet:

plane spritesheet sample

Here's a video file of the looping animation:

The spritesheet is set as a background-image of an empty div element, and the background-position is animated with CSS keyframes using the steps animation-timing-function. This timing function moves the spritesheet one notch every given lapse of time, like a film projector, which achieves the appearance of motion. Here's a code sample:

#plane{
    width: 230px;
    height: 100px;
    background-image: url(plane_roll_spritesheet.png);
    animation: roll 12s steps(215) infinite;
}

@keyframes roll {
    from {background-position: 0px -43000px;}
    to   {background-position: 0px 0px;}
}

In this example, steps moves the spritesheet in the #plane container 43000 pixels up, in 215 discrete steps, for a total duration of 12 seconds, and then repeats indefinitely.

The plane motion wasn't achieved with the spritesheet alone. I found that the plane needs to move about within the scene to get a more lively and believable effect. I added a zoom-in/zoom-out animation as well as a translation using transform: scale() translateX(), and applied it to a container div wrapping the plane, since I couldn't apply two different CSS animations to the same element.

The clouds, the canyon and the trees are animated using CSS transforms. They are positioned far outside the scene, then brought into the scene by keyframing their transform: translateX() property. They appear from the left, move past the scene to the right, then jump back and repeat indefinitely. A slightly different starting position and animation speed for each element generates interesting overlapping patterns.

The background landscape is animated by keyframing its background-position. Unlike the clouds, the canyon and the trees, the landscape is always visible on screen, so I couldn't use the higher performance CSS transform properties. The background image is set to repeat-x, and it scrolls from left to right inside the landscape container indefinitely. See the code below:

#background_landscape{
    position: absolute;
    width: 2536px;
    height: 46px;
    bottom: -1px;
    background-image: url(background_landscape.png);
    background-repeat: repeat-x;
    background-position: center bottom;
    background-size: 2536px 46px;
    z-index: +1;
    opacity: 0.9;
    animation: background_landscape 150s infinite linear;
}

@keyframes background_landscape {
    from {background-position: 0;}
    to   {background-position: 2536px;}
}

Notice how the second keyframe background-position: 2536px is an exact multiple of the landscape container's width (in this case they are identical). This ensures the background image completes a full translate across the container before jumping back to zero.

Also, I found that when that container has bottom: 0, there is an undesirable visible line along the upper border of the div on Chrome for Android. Setting bottom to -1px solves the problem.


Overall, fine tunning the CSS animation values for each element was the result of a trial and error process until things looked right. The only hard rule was to respect the parallax: the apparent motion of farther elements (like the landscape) should be slower than the apparent motion of closer elements (like the trees).

Layout

I wanted to get a cinematic view of the ever running landscape: the scene should span horizontally across the screen, be vertically centered, and black bars be flancked above and below to fill the rest of the space. In addition, the animation should play well across all screen sizes.

cinematic layout

Cinematic view. CSS Flexbox is the solution that worked best to acheive the cinematic layout. Here's the HTML and CSS setup:

<div id="container">

    <header id="header"></header>

    <div id="scene"></div>

    <footer id="footer"></footer>

</div>
#container{
    max-width: 1920px;
    height: 100vh;
    margin: auto;
    display: flex;
    flex-flow: column;
}

#header{
    flex: 1;
    align-self: center;
}

#scene{
    flex: 3;
    max-height: 720px;
    min-height: 460px;
}

#footer{
    flex: 1;
    align-self: center;
}

In the code above, a container is set to span across the full height of the viewport with height: 100vh, to display its child elements as flex elements with display: flex, and to distribute these elements vertically with flex-flow: column.

Each one of the 3 child elements (header, scene and footer) has a flex property. The header and the footer have a flex: 1 and the scene has a flex: 3. This means the viewport height will be divided by 1+3+1=5, and then distributed to each element according to their flex property: 1 fraction for the header, 3 fractions for the scene, and 1 fraction for the footer. Tip: I found that the content was slightly overflowing beyond the viewport on Internet Explorer 11, which triggered scroll bars to appear. Adding align-self: center to both the header and the footer fixes the issue.

This setup achieves the desired view and black bars layout. But additional code is required to adapt the scene to different screen sizes.

Responsive. The scene view has to adapt to a wide range of viewports height and width. Since this animation isn't a video, I can't control the aspect ratio and composition of the frame like in film. Instead, I had to allow for a range of tolerance within which the composition is acceptable.

The initial framing was set on a big desktop screen—this animation was started in 2012. But when testing on shorter and narrower mobile screens, the scene didn't adapt well: the plane was too big, it would overlap over the canyon, the trees would completely block the view, and the whole scene would look cramped.

The first fix was to assign a minimum and maximum height to the scene container. That kept the natural structure of the landscape in check: the sky is neither too high nor too low, the clouds stay on top, and the plane flies above the canyon.

The second step was to define a breakpoint that maintains the overall proportions of the scene. The breakpoint scales every single element of the scene by one half, and it kicks in below either a specific viewport width or height. Here's a code example:

@media screen and (max-width: 1024px), screen and (max-height: 920px){
    #scene{
        min-height: 360px;
        max-height: 460px;
    }

    .clouds{
        animation-name: clouds_small;
    }
}

@keyframes clouds_small {
    from {transform: translate(-1520px) scale(0.5);}
    to   {transform: translate(2560px) scale(0.5);}
}

In this code, the breakpoint is defined at either 1024 pixels wide or 920 pixels high. Below these thresholds, the scene has specific min-height and max-height values, and the clouds CSS animation includes a new parameter scale(0.5).

I played with the CSS positioning and the keyframe values of all the elements to ensure the scene composition remains acceptable on different screen sizes. In fact, giving up static framing in favor of responsive framing generates interesting results. We can get different yet still familiar looks depending on the window size. Here's a variety of views from different screen sizes:

responsive composition

Notice how the upper black bar has disappeared on the last 2 views. This is actually a welcomed behavior generated for free by the combination of min-height and CSS Flexbox: when the viewport height shrinks below a threshold, the browser maintains the scene minimum height, while collapsing the flex header which has no content. This allows the scene to display on top on short screens, which is good.

Typography. The main title is set in Bernard MT Condensed, a nice black typeface reminiscent of western movies. The font is loaded via @font-face. For the credits, I was looking for a movie poster effect. I set the text in Futura with various weights, and played around with line-height and letter-spacing until I got a balance that looked right. Tip: if you want to control the line width while using letter-spacing, subtract an equal value from the margin-right of each line, because the letter spacing is also added to the right of the last character on the line.

Sound

This is the hackiest part. It's so ugly it's beautiful.

I thought about adding music to the animation late in the process. "Volare" by Gipsy Kings was a perfect fit. But there was a problem: I couldn't just include an MP3 copy of the song because of copyright concerns. So how can I play the music?

YouTube! I could stream the music from YouTube by embedding a YouTube player. I could hide the player with CSS and programmatically play/stop the music with Javascript using the YouTube API—except it doesn't work on iOS devices: "to prevent unsolicited downloads over cellular networks at the user’s expense, embedded media cannot be played automatically in Safari on iOS—the user always initiates playback". This means that on iOS, Javascript playback is impossible until the user explicitly taps the YouTube player.

The ugly hack to circumvent this is to position the player under a custom play button and set its opacity to 0:

embedded youtube player

When you click "music", you actually click the YouTube player. Once the music starts playing, a CSS class with pointer-events:none is applied to the player. From there on, play/pause is handled with Javascript.

That is clearly a dark UI pattern. But this animation is a modest experiment and not a production website. I thought it was acceptable to implement such a pattern for the sake of a clean appearance. Tip: on iOS, tapping the YouTube player may automatically trigger fullscreen mode. Add playsinline: '1' to the player variables via the YouTube API to prevent that.

Tips

If you are a designer or developer, here are a list of tips you might find useful:

Watch the animation