# 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 fortranslateX(),translateZ()andtranslateZ()- 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 forscaleX(),scaleZ()andscaleZ()- 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 axisrotate3d()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 the
transform-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
4div.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!