Animated 3D Pyramid With CSS3 and SASS

Datetime:2016-08-23 03:06:32          Topic: CSS3           Share

Below is the end result of this post or you can see a live demo here . The sass/css of this post can be found on github here .

In the dark ages of at least 4 years ago, jQuery used to be the defacto way of creating smooth transitions between elements but now css animations and their promise of hardware accelaration are becoming a standard in all modern browsers. Vendor prefixed css rules are now disappearing into the ether with the usual exception of internet explorer as the standard becomes ratified.

I’ve only just recently discovered the 3d qualities of css3 and I found it pretty easy to cobble together this rotating cube .

I then tried to create the pyramid in the screenshot above which took me considerably longer to acheive the end result.

CSS Coordinate System

Before diving into the code, it is important to realise that the coordinate system used by css transforms to position elements is slightly different than the 3d coordinate system you may have previously learned in geometry. In the css coordinate system, the y-axis and the z-axis are the positioned the other way round from what I learned in maths with the y-axis acting as the vertical axis and the z-axis acting as the guage to slide elements forwards or backwards from the user.

Pyramid Container

The pyramid will be constructed of five divs with four divs making up the triangular faces and a rectangular div for the base.

Below is the react component that contains the jsx that will render the html.

pyramid.js
export default class Pyramid extends Component {
  render(el, props) {
    return (
      <div className="row">
        <div className="row">
          <div className="pyramid-container col-lg-1 col-md-4 col-xs-4 col-md-offset-5 col-xs-offset-5">
            <div id="pyramid">
              <div className="base"></div>
              <div className="front"></div>
              <div className="back"></div>
              <div className="right"></div>
              <div className="left"></div>
            </div>
          </div>
        </div>
      </div>
    );
  }
};

Line 6 contains the opening tag for the pyramid-parent div that will act as a container for the pyramid structure.

The css for the pyramid-parent is below:

parent.css
.pyramid-parent {
  perspective: 800px;
}

The perspective rule on line 2 defines how the depth of the 3D scene is rendered. Think of perspective as a distance from the viewer to the object. If you apply 3D transforms without setting the perspective, elements appear flattened.

The pyramid-parent element encloses a further div with an id of pyramid that has the following css rules assigned to it:

pyramid.css
#pyramid {
  transform-style: preserve-3d;
}

The transofrm-style rule on line 6 specifies how the children of an element are positioned in 3d space or are simply flattened. The default is flat and a value of preserve-3d instructs the browser to position the elements in 3d-space. Without this property set, the pyramid would appear as a 2d triangle. The screenshot below shows how the pyramid looks without the preserve-3d value set:

CSS Triangles

The first challenge was how to create triangles using only css. Some slight of hand and a bit of css skullduggery is required to create an equilateral triangle like below:

Below is the css that creates the effect:

skullduggery.css
.skullduggery {
  width: 0;
  height: 0;
  border-left: 200px solid transparent;  /* left arrow slant */
  border-right: 200px solid transparent; /* right arrow slant */
  border-bottom: 200px solid #2f2f2f; /* bottom, add background color here */
  font-size: 0;
  line-height: 0;
}

The secret to these triangles is creating giant borders to the two perpendicular sides to the direction you would like the triangle to point. Make the opposite side’s border the same size and background colour. The larger the border, the larger the triangle.

Pyramid Maths

Unsurprisingly, positioning elements in 3D is considerably more difficult than in 2D and thankfully, the trigonomic ratios came to the rescue to correctly judge both the length of the elements and the angles of the pyramid.

Below is an image that labels the important parts of the pyramid:

All positioning takes place around the yellow right angle triangle in the above diagram. I first of all determined that I would like an angle of 60° for the slant angle of the triangle or the angle angle between the apothem and the base. The apothem is the slant height of a lateral face of the pyramid. With this angle and assigining a width to the base of the triangle, I could work out both the height of pyramid and the apothem height.

Once I know this, I can determine the lengths of:

  • apothem = (height of triangle) = (½ Base) / cos(α)
  • height = (½ Base) * tan(α)

Where α = 60° and I took the Base = 270px.

One of the nice features of sass is that we can use variables like you would in a normal programming language to stop repeating the same values in css and also mean I can calculate other values from existing variables, something very lacking in current css.

variables.scss
$base: 270px;
$half-base: ($base / 2);
$apothem: 270px;  //(1/2 base) / cos(theta)
$rotate-X: 30deg;
$base-move: 0 - ($apothem - $half-base);

I am using node-sass and I could not find a way of using the trig functions in the sass. This is possible with compass in ruby sass but I don’t konw of a way in node-sass of achieving this so I had to calculate the value of the apothem height in a calculator first.

What I wanted to achieve with these vaiables was to be able to only set the $base width variable and every other value would be derived from that. Sadly as I cannot reference the trigonomic cosine trig function from the sass, I had to manually set the $apothem or slant height variable.

Constructing the pyramid

I will now break down the steps I took to arrange the base and four sides of the pyramid. I will omit the many wrong turns I took in getting here.

Below is another view of the markup that makes up the pyramid:

hyramid.html
<div class="pyramid-container">
  <div id="pyramid">
    <div class="base"></div>
    <div class="front"></div>
    <div class="back"></div>
    <div class="right"></div>
    <div class="left"></div>
  </div>
</div>

Below are the css rules that are assigned to the base div or rectangular base of the pyramid:

base.css
.base {
  position: absolute;
  width: $base;
  height: $base;
  background-color: rgba(147,81,166,0.9);
  transform: rotateX(90deg) translate3d(0px, 0px, $base-move);
  opacity: .5;

  &:after {
    content: "5";
    left: 112px !important;
    top: 93px !important;
  }}

The width and height of the div are set on lines 3 and 4 and the transform property on line 6 is arguably the most important css3 property when it comes to positioning elements in the 3D space. This property allows you to rotate or move elements in the x , y or z 3d coordinate axis. With the base div, I use rotateX to rotate the element 90° along the x or horizontal axis axis to give the impression the div is lying flat and viewed at an angle.

The translate3d property is also used to move the div along the z axis. The translate3d property takes 3 values that can be used to move the element along the x , y or z axis respectively. In this example I am using the $base-move variable that was derived from the base div width to shift the div away from the user along the z axis.

base.scss
$base-move: 0 - ($apothem - $half-base);
.base {
  transform: rotateX(90deg) translate3d(0px, 0px, $base-move);
}

This shifts the div along the z axis or appears to move the div away from the user. This value is important when it comes to positioning the four bases of the triangle divs. Below is how the base looks with these rules applied:

The following generic rules are applied to the triangle divs:

traingles.scss
#pyramid div:not(.base) {
  position: absolute;
  border-left: $half-base solid transparent;
  border-right: $half-base solid transparent;
  border-bottom: $apothem solid;
  transform-origin: $half-base $apothem 0; /* bottom of trangle (1/2 Base, Apothem) */
  opacity: .5;
}

The transform-origin property on line 6 provides a convenient way to control the origin about which transforms using the css transform are applied.

Below is how the front face of the pyramid looks without the transform-origin property set:

And below is a screenshot with it set:

Each face will be moved along the x or horizontal axis by half the base width and will be moved down the y axis by the apothem or slant height. transform-origin must be used with the transform property as it only changes the positioning of transformed elements.

Each triangle will be rotated by a multiple of 90° to orient each triangle for a different face of the pyramid (respctively 0°, 90°, 180°, 270°).

With this in mind, each triangle will have its own rules to set this, in the case of the front face, the following css properties are set:

front.scss
#pyramid div.front {
  border-bottom-color: #e04545;
  transform: rotateY(0deg) translate3d(0px, 0px, $half-base) rotateX($rotate-X);

  &:after {
    content: "1";
  }
}

The next face has the same rules only it the rotateY property is increased by 90°.

back.scss
#pyramid div.back {
  border-bottom-color: #ccaf5a;
  transform: rotateY(90deg) translate3d(0px, 0px, $half-base) rotateX($rotate-X);

  &:after {
    content: "2";
  }
}

Until all triangles are positioned:

Animating the pyramid

The keyFrames rules allows you to gradually change one set of css rules for another which is specified in the from and to properties in the code below:

keyframes.scss
@keyframes spin {
  from {
    transform: rotateY(0deg);
  }
  to {
    transform: rotateY(360deg);
  }
}

In the above code the a gradual rotation around the y axis is specified by starting at 0 degrees and completing at 360 degrees. Once the keyframes rules are specified you then need to associate it with an element via the animation property.

animation.scss
#pyramid {
  animation: spin 8s infinite linear;
}

The name of the keyframes animation is specified along with the length of the animation. The infinite values states that the animation will continue infinitely and linear specifies that the animation speed is constant throughout the animation.





About List