DEV Community

Cover image for CSS 3D
Yuriy Markov
Yuriy Markov

Posted on • Updated on • Originally published at scipios.netlify.com

CSS 3D

Buy Me A Coffee

Thanks to the @rolandcsibrei idea, I've updated this post.

As a frontend developer, I'm working a lot with CSS.

From time to time, I'm experimenting with CSS just for fun.

Recently I've found an article about 3D transforms in CSS, which gave me ideas for new experiments.

To be short, I've finished up with the idea to build an interactive 3D object.

The idea behind the rendering process is simple.

Layout

Plane

There is an object (I refer to it as a plane) that defines the height and width of the object. By default the plane is invisible.

<div class="plane"></div>
.plane {
  transform-style: preserve-3d;
  animation: rotate 6s linear infinite;
}

@keyframes rotate {
  from {
    transform: rotate3d(1, 1, 1, 0deg);
  }
  to {
    transform: rotate3d(1, 1, 1, 360deg);
  }
}

The only rotating object is the plane. All other parts are just following the plane.

Face

Next, I'm creating other objects (I refer to them as faces). Each face is placed in the correspondent place with the respective angle.

<div class="plane">
  <div class="face"></div>
  <div class="face"></div>
  <div class="face"></div>
  <div class="face"></div>
  <div class="face"></div>
  <div class="face"></div>
</div>
.face {
  background: #ffffff11;
  border: 1px solid #ffffff22;
  box-shadow: inset 0 0 8px 8px #ffffff22;
  position: absolute;
  overflow: hidden;
}

Code

The object is enclosed into the space described by three properties: width, height, and depth.

Within the described space I can place from one to N parts (I refer to them as bars). Each bar consists of six faces. Bars are placed from top to bottom along the plane.

Each face must be properly configured to form an object.

The configuration includes such settings as width, height, rotation, and translation.

Constants

I've defined the order of faces as well as amount of them as constants to use them later:

const faceFront = 0;
const faceBack = 1;
const faceRight = 2;
const faceLeft = 3;
const faceTop = 4;
const faceBottom = 5;

const faces = 6;

Calculating face size

To correctly calculate the size of the face I'm using this simple helper function.

/**
 * @param {number} order
 * @param {number} faceSteps
 * @param {number} width
 * @param {number} height
 * @param {number} depth
 * @return {[number, number]}
 */
function calcSize(order, faceSteps, width, height, depth) {
  switch (order) {
    case faceFront:
    case faceBack:
      return [width, height / faceSteps];
    case faceRight:
    case faceLeft:
      return [depth * 2, height / faceSteps];
    case faceTop:
    case faceBottom:
      return [width, depth * 2];
  }
}

This function returns the width and height as an array of numbers depending on the face order and the settings of the object.

Calculating transforms

This function generates the transformation rule from given parameters.

/**
 * @param {number} order
 * @param {number} nHeight
 * @param {number} nSizeY
 * @param {number} planeDepth
 * @param {number} planeWidth
 * @param {number} sizeX
 * @param {number} faceHeight
 * @return {string}
 */
function transform(
  order,
  nHeight,
  nSizeY,
  planeDepth,
  planeWidth,
  sizeX,
  faceHeight
) {
  switch (order) {
    case faceFront:
      return `translate3d(0, ${nHeight}px, ${planeDepth}px)`;
    case faceBack:
      return `rotateY(180deg) translate3d(0, ${nHeight}px, ${planeDepth}px)`;
    case faceRight:
      return `rotateY(90deg) translate3d(0, ${nHeight}px, ${sizeX / -2}px)`;
    case faceLeft:
      return `rotateY(-90deg) translate3d(0, ${nHeight}px, ${sizeX / 2 -
        planeWidth}px)`;
    case faceTop:
      return `rotateX(90deg) translate3d(0, 0, ${nSizeY - nHeight}px)`;
    case faceBottom:
      return `rotateX(-90deg) translate3d(0, 0, ${nHeight +
        faceHeight -
        nSizeY}px)`;
  }
}

These rules are used to place the faces into their respective positions and turning them to the required angle.

Configuring the face

The configure function will apply calculated sizes to the plane as well as transformations.

/**
 * @param {HTMLDivElement} face
 * @param {number} faceNumber
 * @param {number} faceHeight
 * @param {number} faceStep
 * @param {number} planeWidth
 * @param {number} planeHeight
 * @param {number} planeDepth
 */
function configure(
  face,
  faceNumber,
  faceHeight,
  faceStep,
  planeWidth,
  planeHeight,
  planeDepth
) {
  const order = faceNumber % faces;
  const nHeight = ((faceNumber - order) / 3) * faceHeight;
  const [sizeX, sizeY] = calcSize(
    order,
    faceStep,
    planeWidth,
    planeHeight,
    planeDepth
  );
  const nSizeY = sizeY / 2;

  face.className = "face";
  face.style.width = `${sizeX}px`;
  face.style.height = `${sizeY}px`;
  face.style.transform = transform(
    order,
    nHeight,
    nSizeY,
    planeDepth,
    planeWidth,
    sizeX,
    faceHeight
  );
}

Later on, the configured face will be appended to the respective plane to create a bar.

Building the object

Let's wrap it up in the build function.

/**
 * @param {HTMLDivElement} container
 * @param {number} bars
 * @param {number} width
 * @param {number} height
 * @param {number} depth
 */
function build(container, bars, width, height, depth) {
  if (!container) {
    return;
  }

  container.style.width = `${width}px`;
  container.style.height = `${height}px`;

  const planeWidth = width / 2;
  const planeHeight = height / 2;
  const planeDepth = depth / 2;
  const faceStep = bars * 2 - 1;
  const faceHeight = planeHeight / faceStep;

  const plane = document.createElement("div");

  plane.className = "plane";
  plane.style.width = `${planeWidth}px`;
  plane.style.height = `${planeHeight}px`;

  for (var i = 0; i < bars * faces; i++) {
    const face = document.createElement("div");

    configure(
      face,
      i,
      faceHeight,
      faceStep,
      planeWidth,
      planeHeight,
      planeDepth
    );

    plane.appendChild(face);
  }

  container.appendChild(plane);
}

The build function accepts initial settings of an object: parent container, number of bars, width, height, and depth of the object.

This function creates the plane and after that builds faces and appending them to the plane.

After all of the faces are built, the plane is appending to the provided container.

The source code of the working demo is available here neon 3D bars demo (updated).

Adding perspective (update)

As per my example, the 3D object is placed into the container.

To add the perspective, I'm applying the respective CSS rule to the container like follows:

container.style.perspective = "500px";

Later on, the plane will be appended to this container, and the effect of the perspective will make the look of the object even fancier!

const root = document.getElementById("root");
const container = document.createElement("div");
container.className = "container";
container.style.perspective = "500px";
build(container, 3, 500, 500, 250);
root.appendChild(container);

Worth to mention, that in the original example the perspective CSS rule was applied to the plane, but after a few experiments, I've decided to apply it to the container.

So, don't be afraid to experiment and apply this rule somewhere else, maybe even in several places!

Details

If for some reason, your browser doesn't support the yet experimental CSS feature transform-style there would be no 3D effect, but projections will still be visible.

Static parts reside in the CSS definitions. And the dynamic parts are calculated upon each parameter update.

Conclusion

With this new feature, it is possible to enrich web pages with simple 3D objects.

I've got a couple of ideas about how it can be applied!

It can be a preloader to your web application.

It is possible to visualize some data.

Also, it is possible to implement some fancy 3D spinners/waiters/loading indicators!

Top comments (21)

Collapse
 
i_samayorinde profile image
Sam Ayorinde{Blessed is He that Codes} 🇳🇬

Hello, for some reasons I can't explain, I can't say the chrome browser is not supporting transform-style but there's no 3d effect and the project is not visible.

Collapse
 
peacefullatom profile image
Yuriy Markov

Can you show me your code?

Collapse
 
i_samayorinde profile image
Sam Ayorinde{Blessed is He that Codes} 🇳🇬

Initially, I read the code, wrote it the way you did. I was thinking of writing in my own way but wanted to try yours out first.

Thread Thread
 
peacefullatom profile image
Yuriy Markov

Alright. Have you tried this demo gist.github.com/peacefullatom/6568... ?

Thread Thread
 
i_samayorinde profile image
Sam Ayorinde{Blessed is He that Codes} 🇳🇬

It worked. thanks.

Thread Thread
 
peacefullatom profile image
Yuriy Markov

You're welcome! 👍
If you have any questions, feel free to ask me.

Collapse
 
rolandcsibrei profile image
Roland Csibrei

Hey Yuriy, interesting topic! I am definitely going to try this out.

Collapse
 
peacefullatom profile image
Yuriy Markov

You are welcome! If you have any questions/improvements/anything else - just write to me!

Collapse
 
rolandcsibrei profile image
Roland Csibrei

I would consider adding perspective, so the further bars will look smaller.

Thread Thread
 
peacefullatom profile image
Yuriy Markov

Thank you! I've forgotten about this feature. I'll add it to the article and to the demo.

Collapse
 
i_samayorinde profile image
Sam Ayorinde{Blessed is He that Codes} 🇳🇬

Thanks for sharing. I will like to if I can this on my repo on github.

Collapse
 
peacefullatom profile image
Yuriy Markov • Edited

You are welcome! 👍
Only mention the source, if possible. Thanks in advance.

Collapse
 
i_samayorinde profile image
Sam Ayorinde{Blessed is He that Codes} 🇳🇬

Thanks for sharing. Can I put this on my github repo after trying it out?

Collapse
 
peacefullatom profile image
Yuriy Markov • Edited

Yes! 👍
Only mention the source, if possible. Thanks in advance.

Collapse
 
i_samayorinde profile image
Sam Ayorinde{Blessed is He that Codes} 🇳🇬

Ohhh...
Coolies!!

Thread Thread
 
peacefullatom profile image
Yuriy Markov

You are welcome!

Collapse
 
equinusocio profile image
Mattia Astorino

Good! If you use css custom properties you can save a lot of code.

Collapse
 
peacefullatom profile image
Yuriy Markov

Thank you!

Collapse
 
_morgan_adams_ profile image
morgana

Thanks for sharing! This derailed me from my other plans and got me tinkering with transforms :)

Collapse
 
peacefullatom profile image
Yuriy Markov

You are welcome!

Collapse
 
peacefullatom profile image
Yuriy Markov

Thank you @rolandcsibrei ! I've just updated this post. Hope you'll like it!