News

How to use CSS3 to create an animated 3D menu

Legwork’s Sean Klassen reveals the key stages behind creating the main CSS3 3D-powered menu on runbetter.newtonrunning.com

newtonrunning

INSPIRATION: runbetter.newtonrunning.com

Setting the stage

The main menu for the site starts with a background image featuring a Gradient Overlay and a Lens Flare effect, applied with Photoshop. Gradient Overlay allows you to dither the gradient, reducing gradient banding and produce a much smoother gradient upon image compression. Then, using custom code, we generate 13 <div> elements with a white background and low opacity, randomly positioned and rotated in 3D space to complete the stage. This results in a different pattern each time a user revisits the site.

001    .plane {
002      position: absolute;
003      top: 50%;
004      left: 50%;
005      width: 6000px;
006      height: 3000px;
007      margin: -1500px 0 0 -3000px;
008      background-color: #fff;
009      opacity: 0.08;
010    }
011    for (var i = 0; i <= number_of_planes; i++) {
012      $nav_bg.append(‘<div class=”plane”
style=”’ + NPL_ENV.util.transform + 
‘: translate3d(‘ + _.random(-2000, 
2000) + ‘px, ‘ + _.random(-1500, 
1500) + ‘px, ‘ + _.random(-4500, 
-5000) + ‘px) rotateX(‘ + 
_.random(-60, 60) + ‘deg) rotateY(‘ 
+ _.random(-60, 60) + ‘deg) 
rotateZ(‘ + _.random(-60, 60) + 
‘deg);”></div>’);
013    }

Construct the scene

The scene lays in front of the background in Z space (z-index). To enhance the feeling that the user is in a 3D world, we generate 100 particles that are randomly positioned, rotated and coloured in 3D space. When a user flies through the world, the particles are what make it feel 3D. Next, we place the menu items inside this scene to complete the effect.

001    .particle {
002      position: absolute;
003      top: 50%;
004      left: 50%;
005      width: 0px;
006      height: 0px;
007      margin: -12px 0 0 -17px;
008      border-left: 24px solid transparent;
009      border-right: 24px solid transparent;
010      border-bottom: 24px solid #fff;
011    }
012    
013    for (var i = 0; i <= number_of_
particles; i++) {
014      $particles.append(‘<div class=”particle” style=”opacity: 
‘ + Math.random() + ‘; ‘ + NPL_ENV.util.transform + ‘: translate3d(‘ 
+ _.random(-2000, 2000) + ‘px, ‘ 
+ _.random(-1500, 1500) + ‘px, ‘ 
+ _.random(-4000, -10) + ‘px) 
rotateX(‘ + _.random(-60, 60) 
+ ‘deg) rotateY(‘ + _.random(-60, 
60) + ‘deg) rotateZ(‘ + _.random
(-60, 60) + ‘deg);”></div>’);
015    }

Active menu

In order to move into the active menu state, we added a CSS transition on the 3D transform of each button. Then we simply zero out the transforms aside from the one that rotates the item 45 degrees. This makes each item animate from its place in our 3D scene to the foreground so the user can interact with it. The borders and image fades into the background on hover are also achieved with a simple CSS transition.

001    .zone {
002      ...
003      transition: all 800ms cubic-bezier(0.645, 0.045, 0.355, 1.000);
004    }

Fly to section

The final stage is to fly to a section after a user has clicked a menu item. To do this, we write the 3D transform back on to the menu items. The transition makes them animate back into 3D space. Next, we transform the entire scene again with a CSS transition by applying values exactly opposite (and in the opposite order) of the selected menu item and zoom the selected menu item. The transitions meet to give the user the feeling that they are flying through the scene while the selected item is filling up the screen.

001    .zone {
002        ...
003        transition: all 800ms cubic-bezier(0.645, 0.045, 0.355, 1.000);
004    }
005    .zone#zone-4.fly {
006        transform: rotateX(10deg) rotateY(10deg) rotateZ(10deg) translateZ(-4500px);
007    }
008    #scene {
009        ...
010        transform: rotateX(0deg) rotateY(0deg) rotateZ(0deg) translateZ(0px);
011        transition: transform 800ms cubic-bezier(0.645, 0.045, 0.355, 1.000);
012    }
013    #scene.zone-4 {
014        transform: translateZ(4500px) rotateZ(-10deg) rotateY(-10deg) rotateX(-10deg);
015    }
016



×