# 3D Transforms (optional)
NEED-TO-KNOW
- Only 2D transforms are the subject matter to be mastered for this course
- 3D transforms are NOT PART OF THE SUBJECT MATTER to be mastered for this course, but they may challenge you or inspire you for future projects
REMARKS
- The content on this page requires quite a bit of spatial insight
- In order to demonstrate some 3D properties and methods, we make use of transitions and animations
- First go through the pages on 2D Transforms and Animations before you read on
 
- The 2D and 3D transform methods and transform-related properties are:
| 2D and 3D | 3D only | 
|---|---|
| translate(unit ,[unit]),translateX(unit),translateY(unit) | translateZ(unit),translate3d(tX, tY, tZ) | 
| rotate(angle),rotateX(angle),rotateY(angle) | rotateZ(angle),translate3d(nr, nr, nr, angle) | 
| scale(nr [, nr]),scaleX(nr),scaleY(nr) | scaleZ(nr),scale3d(sX, sY, sZ) | 
| skew(angle),skewX(angle),skewY(angle) | |
| transform-origin: unit, unit | perspective-origin: unit, unit | 
| backface-visibility: visible | hidden | |
| perspective(unit)(on element itself) | |
| perspective: unit(on parent element) | |
| transform-style: flat | preserve-3d | 
# translateZ() and translate3d()
- translateZ(): moves an element on the Z axis- Positive arguments brings the object closer to you
- Negative arguments pushes the object further away from you
 
- translate3d()is a shorthand for- translateX(),- translateZ()and- translateZ()- For example: translate3d(20px, 0, 3rem)=translateX(20px) translateY(0) translateZ(3rem)
 
- For example: 
REMARK
- translateZ()doesn't do anything at all until you add some kind of perspective- You can try this on our interactive transform page
 
# scaleZ() and scale3d()
- scaleZ(): scales an element up or down along the Z axis- Arguments larger than 1 scales the object up (zoom in)
- Arguments smaller than 1 scales the object down (zoom out)
 
- scale3d()is a shorthand for- scaleX(),- scaleZ()and- scaleZ()- For example: scale3d(1, 1.5, .7)=scaleX(1) scaleY(1.5) scaleZ(.7)
 
- For example: 
REMARK
- scaleZ()doesn't do anything at all until you add some kind of perspective- You can try this on our interactive transform page
 
# rotateZ() and rotate3d()
- rotateZ(): rotates an element around the Z axis
- rotate3d()is specified by three numbers for the X, Y and Z axes and one angle for the rotation
REMARKS
- The rotate3d()property is NOT a list of X, Y and Z rotations liketranslate3d()andscale3d()
- The three arguments together form a matrix from which ONE axis is calculated (the details of this calculation are beyond the scope of this page) around which the fourth argument (the angle) will rotate
# perspective() method
- Perspective is an important feature to get a natural sense of depth, especially when rotating over one of the axes
- The perspective defines how far your eyes are away from the object - A larger number means that you are further away from the object and the perspective looks smaller
- A smaller number means that you are closer to the object and the perspective looks larger
 
- There are two ways to generate some depth:
- The perspective()method as a value oftransformgives depth to that specific element
- The perspectiveproperty gives depth on a total scene (that is, on a group of child elements)
 
- The 
- Remember that when we use rotateX()androtateY()to rotate along the X and/or Y axis, it looks like the elements are squeezed because, by default, they have no depth
- By adding the perspective()method in front ofrotateX()androtateY(), you enter the 3D space for this specific element and the rotation looks more natural
- In the example below:
- The left animation has no perspective()method
 #without div span { animation: withoutPerspective 4s linear infinite; } @keyframes withoutPerspective { to { transform: rotateX(0); } to { transform: rotateX(360deg); } }1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11- The right animation has the perspective(200px)method IN FRONT OF the rotation method
 #with div span { animation: withoutPerspective 4s linear infinite; } @keyframes withPerspective { to { transform: perspective(200px) rotateX(0); } to { transform: perspective(200px) rotateX(360deg); } }1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
- The left animation has no 
    
<section id="without">
    <div>
        <span>without perspective</span>
    </div>
</section>
<section id="with">
    <div>
        <span>with perspective</span>
    </div>
</section>
     
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
html {
    font-size: 16px;
}
body {
    font-family: Verdana, Geneva, sans-serif;
    line-height: 1.5;
    margin: 2rem;
    display: flex;
    flex-wrap: wrap;
    justify-content: center;
}
section {
    display: flex;
    flex-wrap: wrap;
}
div {
    border: 1px solid darkgray;
    background-color: rgba(169, 169, 169, .2);
    display: inline-block;
    margin: 1rem;
}
span {
    display: inline-block;
    width: 150px;
    line-height: 150px;
    font-size: .8rem;
    text-align: center;
    border: 1px solid #000;
    background-color: rgba(248, 104, 52, 0.6);
}
#without div span {
    animation: withoutPerspective 4s linear infinite;
}
@keyframes withoutPerspective {
    from {
        transform: rotateX(0);
    }
    to {
        transform: rotateX(360deg);
    }
}
#with div span {
    animation: withPerspective 4s linear infinite;
    /* transform-origin: right; */
}
@keyframes withPerspective {
    from {
        transform: perspective(200px) rotateX(0);
    }
    to {
        transform: perspective(200px) rotateX(360deg);
    }
}
 # Exercises
- Play with the different perspective values, e.g. perspective(500px),perspective(100px)
- Change the rotation axis from rotateX()torotateY()
- Change thetransform-originto another rotation point, for example:
#with div span {
    animation: withPerspective 4s linear infinite;
    transform-origin: right;
}
1
2
3
4
2
3
4
Note that, by specifying only one value for transform-origin, the other value defaults to center
# perspective property
- Sometimes you have one object that contains two or more child elements (let's call it a "scene")
- When using the perspective()method on every child element inside the "scene", this doesn't look very natural at all ...
span {
    ...
    animation: rotate 4s linear infinite;
    transform-origin: center;
}
div:hover span {
    animation-play-state: paused;
}
@keyframes rotate {
    from {
        transform: perspective(500px) rotateY(0); 
    }
    to {
        transform: perspective(500px) rotateY(360deg);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
    
<div>
    <span>1</span>
    <span>2</span>
    <span>3</span>
    <span>4</span>
    <span>5</span>
    <span>6</span>
    <span>7</span>
    <span>8</span>
    <span>9</span>
</div>
     
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
html {
    font-size: 16px;
}
body {
    font-family: Verdana, Geneva, sans-serif;
    line-height: 1.5;
    text-align: center;
    height: 100vh;
    display: flex;
    align-content: center;
    justify-items: center;
}
div {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    width: 80vmin;
    height: 80vmin;
    padding: 0 1%;
    margin: auto;
    border: 1px solid darkgray;
    background-color: rgba(169, 169, 169, .2);
}
span {
    height: 30%;
    flex: 0 0 30%;
    font-size: 2rem;
    border: 1px solid #000;
    background-color: rgba(248, 104, 52, 0.6);
    animation: rotate 4s linear infinite;
    transform-origin: center;
}
div:hover span {
    animation-play-state: paused;
}
@keyframes rotate {
    from {
        transform: perspective(500px) rotateY(0);
    }
    to {
        transform: perspective(500px) rotateY(360deg);
    }
}
 - That's where the perspectiveproperty comes in action: when we delete theperspective()method from the child elements and replace it with theperspectiveproperty on the parent (thedivtag) we get a more beautiful 3D perspective for the entire scene
- Now the rotation on the whole scene looks very natural
div {
    ...
    perspective: 500px;               /* add perspective property for the whole scene */
}
span {
    ...
    animation: rotate 4s linear infinite;
    transform-origin: center;
}
div:hover span {
    animation-play-state: paused;
}
@keyframes rotate {
    from {
        transform: rotateY(0);        /* perspective() methode deleted */
    }
    to {
        transform: rotateY(360deg);   /* perspective() methode deleted */
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    
<div>
    <span>1</span>
    <span>2</span>
    <span>3</span>
    <span>4</span>
    <span>5</span>
    <span>6</span>
    <span>7</span>
    <span>8</span>
    <span>9</span>
</div>
     
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
html {
    font-size: 16px;
}
body {
    font-family: Verdana, Geneva, sans-serif;
    line-height: 1.5;
    text-align: center;
    height: 100vh;
    display: flex;
    align-content: center;
    justify-items: center;
}
div {
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    width: 80vmin;
    height: 80vmin;
    padding: 0 1%;
    margin: auto;
    border: 1px solid darkgray;
    background-color: rgba(169, 169, 169, .2);
    perspective: 500px;
}
span {
    height: 30%;
    flex: 0 0 30%;
    font-size: 2rem;
    border: 1px solid #000;
    background-color: rgba(248, 104, 52, 0.6);
    animation: rotate 4s linear infinite;
    transform-origin: center;
}
div:hover span {
  animation-play-state: paused;
}
@keyframes rotate {
    from {
        transform: rotateY(0);
    }
    to {
        transform: rotateY(360deg);
    }
}
 # perspective() vs perspective
# When to use the perspective() method?
 - If only one element has to be transformed, use the perspective()method within thetransformproperty of the element
# When to use the perspective property?
 - If two or more elements have to be transformed, use the perspectiveproperty
- First create a scene container around those elements, e.g. div.scene, and set theperspectiveproperty on this scene
- Attention: the scene itself MAY NOT have any transformation properties!
# perspective-origin
- As you already know: the transform-originproperty sets the reference point for thetransformof a child element
- The perspective-origindoes about the same, but this time for thetransform-originproperty on the whole scene
- So, perspective-origindetermines the position from which you are looking at the whole scene
- The values for perspective-originare exactly the same as fortransform-origin- The default value is also center center
 
- The default value is also 
- In the example below:
- All child elements are rotated 90degalong the X axis
- The whole scene has a perspectiveof300px
- The animation changes the perspective-origin
- The green dot is on the perspective-originand represents the view point from where you are looking at the perspective
 
- All child elements are rotated 
    
<div>
    <span>1</span>
    <span>2</span>
    <span>3</span>
    <span>4</span>
    <span>5</span>
    <span>6</span>
    <span>7</span>
    <span>8</span>
    <span>9</span>
</div>
     
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
html {
    font-size: 16px;
}
body {
    font-family: Verdana, Geneva, sans-serif;
    line-height: 1.5;
    text-align: center;
    height: 100vh;
    display: flex;
    align-content: center;
    justify-items: center;
}
div {
    background-image: url(https://itf-web-essentials.netlify.app/assets/dot.svg);
    background-repeat: no-repeat;
    background-size: 1rem 1rem;
    background-position: 0% 0%;    
    display: flex;
    flex-wrap: wrap;
    justify-content: space-between;
    align-items: center;
    width: 80vmin;
    height: 80vmin;
    padding: 0 1%;
    margin: auto;
    border: 1px solid darkgray;
    background-color: rgba(169, 169, 169, .2);
    perspective: 300px;
    animation: moveOrigin infinite 10s;
}
span {
    height: 30%;
    flex: 0 0 30%;
    font-size: 2rem;
    border: 1px solid #000;
    background-color: rgba(248, 104, 52, 0.6);
    transform: rotateX(90deg);
    /* transform-origin: left; */
}
@keyframes moveOrigin {
    0% {
        perspective-origin: 0% 0%;
        background-position: 0% 0%;
    }
    25% {
        perspective-origin: 100% 0%;
        background-position: 100% 0%;
    }
    33% {
        perspective-origin: 100% 33%;
        background-position: 100% 33%;
    }
    50% {
        perspective-origin: 50% 50%;
        background-position: 50% 50%;
    }
    66% {
        perspective-origin: 66% 100%;
        background-position: 66% 100%;
    }
    75% {
        perspective-origin: 10% 90%;
        background-position: 10% 90%;
    }
    100% {
        perspective-origin: 0% 0%;
        background-position: 0% 0%;
    }
}
 # Exercises
- Enable the transform-originproperty and with some the different settings, e.g.top,bottom, ...
- Modify the value of rotateX()and look what happens
- Rename the property rotateX()torotateY()and look what happens
# transform-style
- The transform-styleproperty determines whether the DIRECT child elements are flattened in its 2D plane or shown in a 3D space
- By default, all direct children are rendered in a flat, 2D plane (transform-styleon parent is defaultflat)
- To allow  all direct children to be transformed on the Z axis (translateZ(),rotateZ()and/orscaleZ()) , you have to EXPLICITLY set the property of the PARENT element totransform-style: preserve-3d
- In the example below:
- Because we are going to transform several elements, we are going to use a scene with a global perspective
 div.scene { ... perspective: 2000px; /* perspective for the whole scene */ }1
 2
 3
 4- div.parentis the first element inside the scene and rotates along its Y axis
 div.parent { ... animation: rotate 5s linear infinite; } @keyframes rotate { from { transform: rotateY(0); } to { transform: rotateY(360deg); } }1
 2
 3
 4
 5
 6
 7
 8- Inside div.parentare two child elements which have a differenttranslateZ()value- The red box has to move 5remcloser to the viewertransform: translateZ(5rem);
- The green box has to move 8remcloser to the viewertransform: translateZ(8rem);
 
- The red box has to move 
 span:first-child { ... transform: translateZ(5rem); } span:last-child { ... transform: translateZ(8rem); }1
 2
 3
 4
 5
 6
 7
 8- As you can see, the two child elements are flattened in their parent element (div.parent) and theirtranslateZ()methods don't work!
 
    
<div class="scene">
    <div class="parent">
        div.parent
        <span>translateZ(5rem)</span>
        <span>translateZ(8rem)</span>
    </div>
</div>
     
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
html {
    font-size: 16px;
}
body {
    font-family: Verdana, Geneva, sans-serif;
    line-height: 1.5;
    margin: 3rem;
}
div.scene {
    outline: 1px solid whitesmoke;
    width: 300px;
    height: 300px;
    margin: auto;
    display: flex;
    justify-content: center;
    align-items: center;
    perspective: 2000px;  /* perspective for the whole scene */
}
div.parent {
    width: 90%;
    height: 90%;
    position: relative;
    border: 1px solid darkgray;
    background-color: rgba(101, 176, 251, 0.2);
    animation: rotate 5s linear infinite;
    /* transform-style: preserve-3d; */
}
@keyframes rotate {
    from { transform: rotateY(0); }
    to { transform: rotateY(360deg); }
}
span {
    position: absolute;
    text-align: center;
    border: 1px solid darkgray;
    font-size: .8rem;
}
span:first-child {
    top: 20px;
    left: 20px;
    width: 200px;
    line-height: 200px;
    background-color: rgba(251, 101, 101, 0.2);
    transform: translateZ(5rem);
}
span:last-child {
    top: 50px;
    left: 50px;
    width: 250px;
    line-height: 100px;
    background-color: rgba(52, 248, 117, 0.6);
    transform: translateZ(8rem);
}
 - All you have to do to fix this behavior is enabling the 3D space on the parent element
div.parent {
    ...
    animation: rotate 5s linear infinite;
    transform-style: preserve-3d;
}
1
2
3
4
5
2
3
4
5
# Examples
# 3D figcaption
- Let's start from a finished transition example, in which a figcaptionis rotated around the X axis (from90deg= invisible to0= visible) when hovering its enclosingfigure
- All you have to do to change the squeezed figcaptionto a nice, foldable version, is adding some perspective to the transformation- Add perspective(300px)as the first method to the transformation and change the start rotation to60deg
 
- Add 
figcaption {
   ...
   transform: perspective(300px) rotateX(60deg);
   ...
}
1
2
3
4
5
2
3
4
5
    
<figure>
    <img src="https://picsum.photos/id/1080/300/300" alt="Strawberries">
    <figcaption>Strawberries</figcaption>
</figure>
     
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
body {
    font-family: Verdana, Geneva, sans-serif;
    line-height: 1.5;
    padding: 1rem;
    text-align: center;
}
figure {
    border: 1px solid darkgray;
    line-height: 0;
    display: inline-block;
    position: relative;
}
figcaption {
    position: absolute;
    bottom: 0;
    left: 0;
    width: 100%;
    font-size: 2rem;
    line-height: 1.5;
    background-color: rgba(255, 255, 255, 0.3);
    color: white;
    transform: rotateX(90deg);  /* change to: perspective(300px) rotateX(60deg) */
    transform-origin: bottom center;
    transition: transform .5s;
}
figure:hover figcaption {
    transform: rotateX(0);
}
 # Flip card
- The flip card (div.scene) contains two children (div.frontanddiv.back)
- By default, the front and the back of the card stand beneath each other
    
<div class="scene">
    <div class="front">
        <h1>Front</h1>
    </div>
    <div class="back">
        <h1>Back</h1>
    </div>
</div>
     
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
html {
    font-size: 16px;
}
body {
    font-family: Verdana, Geneva, sans-serif;
    line-height: 1.5;
    padding: 1rem;
    text-align: center;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
}
h1 {
    margin-top: 3rem;
    color: firebrick;
    /* transform: translateZ(2rem); */
}
.scene {
    /* outline: 1px solid lightgray; */
    /* width: 200px;
    height: 300px;
    position: relative; */
    /* perspective: 500px; */
    /* transform-style: preserve-3d; */
}
.front, .back {
    width: 200px;
    height: 300px;
    border: 1px solid #000;
    box-shadow: 1rem 1rem 2rem rgba(0, 0, 0, .3);
    /* transition: 2s; */
    /*  backface-visibility: hidden; */
    /* position: absolute;
    top: 0;
    left: 0;  */
    /* transform-style: preserve-3d; */
}
.front {
    background-image: url(https://picsum.photos/id/1028/200/300);
}
.back {
    background-image: url(https://picsum.photos/id/1028/200/300?grayscale);
    /* transform: rotateY(180deg); */
}
.scene:hover .front {
    /* transform: rotateY(180deg); */
}
.scene:hover .back {
    /* transform: rotateY(360deg); */
}
 - Step 1: Rotate the back of the card
- At the end only the front is visible in the default state and the back is visible on the hover state
- We have to rotate the back by 180degon the Y axis, to avoid that the text on the reverse side is mirrored in the hover state.back { background-color: rgba(128, 201, 240, 0.6); transform: rotateY(180deg); }1
 2
 3
 4
 
- Step 2: Flip the card
- To flip the whole card, we rotate the front and the back 180degon the hover state of the card (div.scene)
- Because the back already starts from 180degonward, it continues to rotate to360deg.front, .back { ... transition: 2s; } .scene:hover .front { transform: rotateY(180deg); } .scene:hover .back { transform: rotateY(360deg); }1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 
- To flip the whole card, we rotate the front and the back 
- Step 3: Disable the backface
- Only the part of the card that is not mirrored may remain visible
- If we do not do this, we will always see the back when we put the front and the back on top of each other.front, .back { ... transition: 2s; backface-visibility: hidden; }1
 2
 3
 4
 5
 
- Step 4: Position the front and the back on top of each other
- Position the front and the back absolutely inside the card container.scene { width: 200px; height: 300px; position: relative; } .front, .back { ... position: absolute; top: 0; left: 0; }1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 
- Position the front and the back absolutely inside the card container
- Step 5: Add perspective to the scene
- All you have to do to change the squeezed rotation to a nice 3D version, is adding some perspective to the whole scene and not to the individual elements.scene { width: 200px; height: 300px; position: relative; perspective: 500px; }1
 2
 3
 4
 5
 6
 
- All you have to do to change the squeezed rotation to a nice 3D version, is adding some perspective to the whole scene and not to the individual elements
- Final step: Put the text in front of the card
- Translate the text by 2remon the Z axis to place it in front of the rest of the card
- By default, the Z axis is rendered inside a flat surface, that's why we have to explicitly enable 3D on all its parent elements
- The parent element for the text 'front' is div.frontand the parent for the text 'back' isdiv.back
 The parent element fordiv.frontanddiv.backisdiv.sceneh1 { margin-top: 3rem; color: firebrick; transform: translateZ(2rem); } .scene { ... perspective: 500px; transform-style: preserve-3d; } .front, .back { ... transform-style: preserve-3d; }1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 
- Translate the text by 
# Flapping image
- Our scene contains two levels of nested elements, and they both contain their own animation
- The figurerotates around the X axis
- The img, inside thefigure, "flaps" on the top of thefigure
 
- The 
    
<div class="scene">
    <figure>
        <img src="https://picsum.photos/id/855/300/200" alt="">
        <figcaption>Photo @ Rodion Kutsaev</figcaption>
    </figure>
</div>
     
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}
html {
    font-size: 16px;
}
body {
    font-family: Verdana, Geneva, sans-serif;
    line-height: 1.5;
    height: 100vh;
    display: flex;
    justify-content: center;
    align-items: center;
    font-weight: bold;
}
.scene {
    /* perspective: 1000px;*/
}
figure {
    text-align: center;
    width: 300px;
    margin: auto;
    border: 1px solid #000;
    border-top-width: 1rem;
    /* transform-style: preserve-3d; */
}
.scene figure {
    /* animation: rotateFigure 10s linear infinite; */
}
.scene:hover figure {
    /* animation-play-state: paused; */
}
@keyframes rotateFigure {
    from { transform: rotateY(0); }    
    to { transform: rotateY(360deg); }
}
img {
    vertical-align: middle;
    width: 100%;
    height: auto;
    /* animation: flapImage 700ms ease-in-out infinite alternate; 
    transform-origin: top; */
}
@keyframes flapImage {
    from { transform: rotateX(-40deg); }
    to { transform: rotateX(40deg); }
}
 - Step 1: Add perspective to the scene.scene { perspective: 1000px; }1
 2
 3
- Step 2: Let the image "flap"
- Add an alternating animation to the imgtag (rotate between-40degand40deg)
- Set the transform-originto thetopof the image
 img { vertical-align: middle; width: 100%; height: auto; animation: flapImage 700ms ease-in-out infinite alternate; transform-origin: top; } @keyframes flapImage { from { transform: rotateX(-40deg); } to { transform: rotateX(40deg); } }1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
- Add an alternating animation to the 
- Step 3: Rotate the figure
- Rotate the figurearound his Y axis
- Pause the animation on the hover state of the scene
 .scene figure { animation: rotateFigure 10s linear infinite; } .scene:hover figure { animation-play-state: paused; } @keyframes rotateFigure { from { transform: rotateY(0); } to { transform: rotateY(360deg); } }1
 2
 3
 4
 5
 6
 7
 8
 9
 10
- Rotate the 
- Final step: Preserve 3D on the figure- The image stops to flap because the figure has his own transform now!
 (Remember that child elements are rendered on a flat pane by default)
- Enable 3D on the figure
 figure { ... transform-style: preserve-3d; }1
 2
 3
 4
- The image stops to flap because the figure has his own transform now!
