Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Grid of 3d boxes (only two faces)

I have a grid of 3d boxes with only two faces (front and bottom). Each box has its own perspective. On hover, box rotates; bottom face faces the front. For example:

.grid {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    width: 1000px;
    margin: 50px auto;
}
.box-wrapper {
    width: 25%;
    height: 250px;
    perspective: 1000px;
}
.box {
    width: 100%;
    height: 100%;
    position: relative;
    transition: transform .5s;
    transform-style: preserve-3d;
}
.box .face {
    width: 100%;
    height: 100%;
    position: absolute;
    left: 0;
    top: 0;
    border: 1px solid black;
    display: flex;
    justify-content: center;
    align-items: center;
}
.box .face.front {
    transform: translateZ(123.5px);
    background-color: hsla(0, 100%, 50%, .5);
}
.box .face.bottom {
    transform: rotateX(-90deg) translateZ(123.5px);
    background-color: hsla(120, 100%, 50%, .5);
}
.box:hover {
    transform: rotateX(90deg);
}
<div class="grid">
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
</div>

Now, the problem with this setup is the overlaps among boxes. I can get rid of the overlaps manually, and to do that I need to do following changes/calculations:

  • change the width, height, and margins (right and bottom) of the .box-wrapper
  • change the transform (translateZ) of the .face.front and the .face.bottom

and since one change affects the others, this is problematic.

For example, to get rid of the overlaps in the setup above (1000px container), it requires the following changes:

.box-wrapper {
    width: calc(25% - 29px); // why 29?
    margin-right: 29px;
    margin-bottom: 29px;
    height: 221px;
}
.box .face.front {
    transform: translateZ(110.5px); // half of box's width
}
.box .face.bottom {
    transform: rotateX(-90deg) translateZ(110.5px);
}

.grid {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    width: 1000px;
    margin: 50px auto;
}
.box-wrapper {
    width: calc(25% - 29px);
    margin-right: 29px;
    margin-bottom: 29px;
    height: 221px;
    perspective: 1000px;
}
.box {
    width: 100%;
    height: 100%;
    position: relative;
    transition: transform .5s;
    transform-style: preserve-3d;
}
.box .face {
    width: 100%;
    height: 100%;
    position: absolute;
    left: 0;
    top: 0;
    border: 1px solid black;
    display: flex;
    justify-content: center;
    align-items: center;
}
.box .face.front {
    transform: translateZ(110.5px);
    background-color: hsla(0, 100%, 50%, .5);
}
.box .face.bottom {
    transform: rotateX(-90deg) translateZ(110.5px);
    background-color: hsla(120, 100%, 50%, .5);
}
.box:hover {
    transform: rotateX(90deg);
}
<div class="grid">
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
</div>

I also plan to make this mobile friendly, so as the window width changes, I'll change the width of the container (.grid), and do the above-mentioned changes. So the only variables I have are the container width which will change with respect to window width, and that a box should occupy 25% of the container width. How can I do this calculation automatically? Since the calculation includes perspective and transforms, I can't come up with anything yet.

Even if I calculate the values (width, margin, transform) manually (trial-error) for large screen breakpoints, at some point I need to do it automatically with JS, for example below 768px.

like image 899
akinuri Avatar asked Jul 26 '18 08:07

akinuri


1 Answers

It's clear that the issue comes from the translateZ combined with perspective. You are making your element closer to you with positive translation and the perspective is creating the overlap.

In order to avoid this we can need avoid the translateZ and use another configuration to create a similar effect. Here is an idea:

.grid {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    width: 1000px;
    margin: 50px auto;
}
.box-wrapper {
    width: 25%;
    height: 250px;
    perspective: 1000px;
}
.box {
    width: 100%;
    height: 100%;
    position: relative;
    transition: transform .5s, transform-origin 0.5s;
    transform-style: preserve-3d;
}
.box .face {
    width: 100%;
    height: 100%;
    position: absolute;
    left: 0;
    top: 0;
    border: 1px solid black;
    display: flex;
    justify-content: center;
    align-items: center;
    box-sizing:border-box;
}
.box .face.front {
    background-color: hsla(0, 100%, 50%, .5);
}
.box .face.bottom {
    transform: rotateX(-90deg) translateY(50%) translateZ(125px);
    background-color: hsla(120, 100%, 50%, .5);
}
.box:hover {
    transform: rotateX(90deg) translateY(-100%);
    transform-origin:top;
}
<div class="grid">
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
</div>

As you can see, there is no more translateZ with the front element so no more overlap initially then I adjust the transform-origin and translation in order to compensate the translateZ applied to the bottom element.

All the values use are relative ones using percentage, so you won't have any issue doing calculation. I also added box-sizing:border-box to include border in the height/width calculation so that you can use exactly use half the height (250px / 2 = 125px) within translateZ.

UPDATE

I added a transition to the transform-origin in order to have a rotation somehow from the center and not completely from the top.

UPDATE 2

As suggested in the comment we can also change transform-orgin to center center -125px

.grid {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;
    width: 1000px;
    margin: 50px auto;
}
.box-wrapper {
    width: 25%;
    height: 250px;
    perspective: 1000px;
}
.box {
    width: 100%;
    height: 100%;
    position: relative;
    transition: transform .5s;
    transform-style: preserve-3d;
    transform-origin:center center -125px;
}
.box .face {
    width: 100%;
    height: 100%;
    position: absolute;
    left: 0;
    top: 0;
    border: 1px solid black;
    display: flex;
    justify-content: center;
    align-items: center;
    box-sizing:border-box;
}
.box .face.front {
    background-color: hsla(0, 100%, 50%, .5);
}
.box .face.bottom {
    transform: rotateX(-90deg) translateY(50%) translateZ(125px);
    background-color: hsla(120, 100%, 50%, .5);
}
.box:hover {
    transform: rotateX(90deg);
}
<div class="grid">
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
    <div class="box-wrapper">
        <div class="box">
            <div class="face front">front face</div>
            <div class="face bottom">bottom-face</div>
        </div>
    </div>
</div>
like image 122
Temani Afif Avatar answered Oct 25 '22 23:10

Temani Afif