CSS in 3d

Contents:


Introduction

Cascading Style Sheets, or CSS, is often viewed as the bane of web design. While it’s relatively easy to learn - there’s generally only one pattern you need to learn, illustrated below - it’s notoriously difficult to master, and there are all sorts of finicky little gotchas that are sure to make you lose at least some of your hair. After all, how can this:

    #my-element{
        position:absolute;
        vertical-align:middle;
        text-align:center;
    }

not just simply do what it says on the box and, you know, put stuff in the middle of the page? The fact that simply typing vertical-align into Google brings up “vertical-align not working” as like the second or third suggestion should itself suggest how very many people have trouble with this little CSS rule 1.

One way of helping to alleviate this frustration is to remember that you’re a coder, and you love coding. So instead of beating your head against some really annoying, finicky CSS, why not try playing with it instead? Why not try making something in 3D, using only CSS?

Before we begin, I’m gonna do two review sections: one of the fundamentals of CSS inheritance, and one of the positioning system.


Review of CSS Inheritance

CSS is so-called cascading because of the way in which elements inherit particular CSS rules. The most general rules are ‘obeyed’, unless there are more specific rules. Just so we know what I’m talking about later on, let’s make a quick list of the order of inheritance. First, we’ll look at where the rules are coming from:

  • None: While it’s pretty much impossible to ‘use’ this, as the user-agent stylesheet takes precedence over it, we could argue that the most basic style is simply no style.
  • User-Agent stylesheet: The most general ruleset in use is the user-agent stylesheet, which is defined by the browser itself. This is why, for example, input elements look different in different browsers. Worth remember, if you need very specific styles.
  • External stylesheets: If we load an external sheet, via a <link> tag, that stylesheet takes precedence over the user-agent sheet.
  • Inline <style> declaration: It’s usually frowned upon (separation of concerns and all that), but if you’re a rebel and choose to include inline <style> elements, these will win over external stylesheets.
  • Inline, in-element declarations: These are the most specific, generally speaking. While they were traditionally frowned upon, as they have the same issue as inline <style> declarations in terms of lack of separation of concerns, they’ve become slightly more popular with dynamically generated views, from stuff like AngularJS or ReactJS.

Within each of these style categories, there are also rules determining what element(s) is/are selected:

  • ‘*’ selector: The ‘everything’ selector. * is a generic wildcard for ‘any element’. Useful if you wanna make very broad changes
  • <tag> selector: This selects every element with a particular tag name. For example, if you said span{ background:#f00; }, every <span> element would now have a bright red background. Note two things here: firstly, making all your span elements have a red background is a surefire way to make people hate you. Second, the actual selector here is just the tag’s name, *not the name plus the < and > symbols.
  • ‘.class’ selector: This selects everything with a particular class. So if we apply a style to class ‘my-class’, then that’d affect <div class='my-class'>, <span class='my-class'>, and <input class='my-class'>, but not <div class='your-class'>. The class selector (almost!) always wins over the tag selector.
  • ‘#id’ selector: The ID selector is the most specific of the general element selectors. Baring any other hierarchy considerations, the ID selector always wins over the class selector.

There are also pseudo-classes, pseudo-selectors, and other miscellanous bits, which can be applied to any of the above four selector types:

  • :hover: Applies a style only when an element has the mouse over it. An example might be something like #my-btn:hover{font-size:larger}, which would make your (presumably button) element with the id my-btn have a larger font-size when hovered over.
  • :focus: Usually used for input elements, this is used when an element has focus. That is, for example, if I’m typing, and the stuff I’m typing is currently going in this particular text input, then this style will affect that element.
  • :active: Don’t ignore this one just because it’s there for such a brief time! The :active pseudo-class applies while an element is being actually used. So if it’s a link, this’ll be active while the mouse is down on it.
  • :visited: Remember the old alink, link and vlink attributes of HTML4 and previous? This is sorta like that old vlink: it changes the style of links, but only if they’ve been visited.
  • ::before: Allows you to create what is essentially a separate element (complete with its own style!) that’s ‘attached’ to the parent element. Note that you’ll usually need to use content:''; to actually give the element something to ‘show’. traditionally goes before the element (that is, above or to the left of)
  • ::after: Same as above, but traditionally goes, well, after the element.
  • !important: Not a pseudo-selector, but I thought it important to include it here anyway. The infamous !important declaration goes on a particular rule, and basically overrides any other heirarchy rules:
  #my-id{
      background-color:#f00;
  }
  .my-class{
      background-color:#0f0!important;
  }

Assuming we have element with both the class ‘my-class’ and the id ‘my-id’, the background color of this element would be green, despite the fact that ID selectors normally take precedence over class selectors. Because it basically overwrites any normally more-specific rulesets, this is frowned upon as a bit of a sledgehammer-to-crack-a-nut approach: it’s far better to rewrite your code so that you don’t have to use this than to use it. 2


Review of CSS Positioning

If you ever wanna position anything with CSS, you’ll need to know about the four different types of positioning, via the eponymous position property.

  • position:static;: This value is very rarely explicitly used, as it’s basically the default value. This basically means that the element ignores all ‘custom’ positioning rules. So if you said:
     #my-div{
         position:static;
         left:100px;
     }

Your element would stubbornly refuse to move right 100 pixels. How rude. Instead, elements with a static declaration will be positioned as per ‘normal’ layout.

  • position:absolute;: In this case, the element is positioned relative to the first parent (going up its DOM heirarchy tree) that itself has a position value of something other than static. As the name implies, it’s useful for positioning elements absolutely.
  • position:relative;: This allows you to position elements relative to their ‘normal’ position. So if you say:
     #my-div{
         position:relative;
         left:10px;
     }

then your element would be 10px to the right of where it ‘normally’ is. 3

  • position:fixed;: This one’s a bit odd: instead of positioning an element relative to a parent, or to its original position, it positions the element relative to the browser window. The cool thing about this affect is that if you position an element as so:
     #my-div{
         position:fixed;
         left:10px;
         top:10px;
     }

and then scroll the window, the element will stay ‘glued’ to the same position. Really useful for stuff like menus.

Note that fixed and absolute also tell the browser not to leave space for the element. That is, if we have three spans, and the middle one has position:fixed;, then the first and third span will be smack up against each other.

You’ll also, however, need to know about the various display property values:

  • display:none;: Does pretty much what it says on the can: doesn’t show anything. Often used behind the scenes with various dynamic view things like AngularJS’s ng-show attribute.
  • display:inline;: Basically only takes up as much with as the element needs. Does not force a new line (akin to <br>) after or before the element. inline also tends to ignore certain positioning rules (as well as width and height), so when in doubt, probably best to choose one of the other rules if you need specific positioning.
  • display:block;: On the contrary, this by default takes up the whole width of the page, and does force a new before and after. Also, does pay attention to positioning rules. Pretty useful, but for elements that aren’t supposed to take up the entire page, the blank space can get kinda annoying.
  • display:inline-block;: The best of both worlds! Doesn’t force annoying linebreaks that create nasty blank space, but also allows you to specify widths and heights. Great for things like menus!
  • display:flex;: I won’t go into this much here, but suffice it to say that flex, part of CSS3’s Flexbox setup, is very useful, and well worth the research.

The Magic: transform-style:preserve-3d; and perspective;

The transform-style property declares how elements are displayed in 3-space. Normally, its default value of flat means that while you can rotate and move elements in 3-space, they’ll be ‘flattened’ against the element with this declaration. That is, if you attempt to dynamically rotate them (via an onmousemove event or something similar), they’ll still appear as part of a flat, z-axis-less plane.

Instead, we need to use the magic rule transform-style:preserve-3d;, which says that any child elements of this element will keep their z-axis (and thus stay 3D!). It’s worth repeating that this does not affect this element itself, but only its children!


How to 3d

After you’ve declared a parent element with a transform-style property of preserve-3d, you just simply have to use the various transform property values to position your element in 3-space. Be aware that while you can rotate and move an element in the third, Z-axis dimension, there is no depth property. That is, you cannot construct a box by doing:

    .my-box{
        width:100px;
        height:100px;
        depth:100px;
    }

instead, you’d have to construct the ‘walls’ of the box by rotating and positioning the individual faces.


Performance

There are generally three pieces of advice useful for doing CSS-3d:

  1. When possible, don’t. Yes, I love doing CSS 3d, but even the best computer/browser is gonna find it a strain.
  2. When possible, use 3d transforms. The reason for this is that, especially with modern browsers, simply using a 3d-transform (even something like transform:rotateZ(30deg), which is visually no different than transform:rotate(30deg)) forces the browser to use hardware accelerated graphics, if available. Since most computer’s graphics cards are better at, well, doing graphics than simple software rendering, this can make stuff much more performant.
  3. Cross-browser testing is important! Depending on exactly what you’re doing, you’re gonna be using some pretty fancy and cutting-edge tech here. Certain browsers (hello again, IE/Edge) haven’t quite kept up with that, so you may run into issues rendering stuff in 3d in those environments.

Alternatives and When to Use

As I mention in point 1 of the previous section, most browsers find this a particular strain, since it’s forcing the browser to make relatively complex calculations. A much faster method would be to use JavaScript and Canvas, which are explicitly designed for graphics.
However, for demonstration purposes, very simple UI effects, and a few other use cases, CSS 3d is fine to use.


Notes:

  1. If you’re wondering why this doesn’t work, it has to do with the fact that the height of a webpage is very rarely explictly defined. Because they’re designed to scroll vertically, webpages basically function on the “if I need more space, just add more vertical height” mantra. For this reason, telling something to be positioned vertically, especially by something like a percent or relative value (rather than, say, an absolute one like “500px”) just won’t work. Take a look at the following example:

        html,body{
            background:#000;//black background;
        }
        .my-div{
            position:absolute;
            width:98%;
            left:1%;
            top:1%;
            height:98%;
            background:#ddf;
        }
    

    This, demonstrably, doesn’t work. If you inspect it, you’ll find a div with the correct width (98% of the total window width), but a height of zero! Instead, we need to explicitly define the height of the document as 100%:

        html,body{
            height:100%;
            width:100%;
            background:#000;//black background;
        }
    
  2. Furthermore, using multiple !important declarations can make your CSS incredibly difficult to debug!

  3. Here’s a perhaps better example. Imagine we have a bunch of <span> elements in a row, contained within a div that has its property set to absolute. If we apply the rule position:relative to the spans, and then add left:0px;, the elements will be positioned as if they had no position styling at all. If we changed that left to say, 5px, the elements would now all be shifted right by 5 pixels from their original locations. Similarly, if we change position:relative to position:absolute, the elements would now all be stacked on top of each, 5 pixels from the left border of the container div.