<!-- this adds a play/pause functionality to the demo, the paused state being the default for a more comfortable viewing experience -->
<input type="checkbox" name="play" id="play">
<!-- this is the main focus of this lesson -->
<div class="scene">
<div class="cube raw">
<div class="wall horizontal"></div>
<div class="wall horizontal"></div>
<div class="wall vertical"></div>
<div class="wall vertical"></div>
<div class="wall applicate"></div>
<div class="wall applicate"></div>
</div>
</div>
<label for="play" class="playBtn"></label>
<div class="scene">
<div class="cube optimized">
<div class="wall horizontal"></div>
<div class="wall horizontal"></div>
<div class="wall vertical"></div>
<div class="wall vertical"></div>
<div class="wall applicate"></div>
<div class="wall applicate"></div>
</div>
</div>
body {
margin: 0; /* this overrides the default margin - which is crucial if your background isn't white/blank/undefined */
box-sizing: border-box; /* another one of those standard overrides that you'll need to put in every darn time */
background-color: black; /* the hex code for dark gray, the equivalent of rgb(48,48,48) or hsl(0deg 0% calc( 100% / 16 *3)), and a shorthard for #333333 )*/
display: flex; /* you could alternately use */
flex-direction: row; /* all children are laid out side by side */
justify-content: center; /* centers every object horizontally */
align-items: center; /* centers every object vertically */
min-height: 100vh; /* we need the viewport (the browser window) to be 100vh (100 viewport height units) so that there is enough space to suspend the object in */
font-size: .25vw; /* sets a fontsize dependent on the width of the viewport */
}
.scene {
width: 100em; /* sets the width to be 100 units of the font size, thus ensuring responsive scaling that mimics percentages */
aspect-ratio: 1; /* makes sure the height matches the width of the container, making it a square*/
perspective: 250em; /* sets the distance of the perspective */
position: relative; /* makes the position of the container independent of any other neighboring container - placed wherever you want rather than relative to its neighbors, and free to overlap */
}
.scene:nth-of-type(2) {
scale: -1 1;
}
.scene * {
transform-style: preserve-3d; /* makes sure that each container nested inside the .scene tag treats its nested content as a 3D object rather than a 2D projection */
position: absolute; /*see absove*/
inset: 0; /* gives the containers nested in the .scene tag a margin of 0, thus making them fill it 100% in width and height; this is shorthand for top, right, bottom, left */
}
.cube {
rotate: y 20deg;
animation: spin 6s linear infinite; /* adds a slow spinning animation */
}
.wall {
opacity: .5;
}
.wall:nth-child(odd) {
--direction: 1; /* variable for the direction of the .wall transforms; this one keeps the value the same */
}
.wall:nth-child(even) {
--direction: -1; /* variable for the direction of the .wall transforms; this one turns value to its negative */
filter: invert(); /* inverts the colors */
}
/* the styles of the left, unoptimized cube */
.raw .horizontal {
background-color: red;
transform: rotateY( calc( 90deg*var(--direction) ) ) translateZ(50em); /* rotate the wall left or right, depending on the variable, then go forward by 50em */
}
.raw .vertical {
background-color: green;
transform: rotateX( calc( 90deg*var(--direction) ) ) translateZ(50em); /* rotate the wall up or down, depending on the variable, then go forward by 50em */
}
.raw .applicate {
background-color:blue;
transform: rotateY( calc( 90deg - 90deg*var(--direction) ) ) translateZ(50em); /* move the wall forward - or back, depending on the variable - by 50em */
}
/* the styles of the right, optimized cube */
.optimized .wall {
--turn: var(--turnAxis);
transform: var(--turn) translateZ(50em);
}
.optimized .horizontal {
background-color: red;
}
.optimized .vertical {
background-color: green;
--turnAxis: rotateX( var(--turnVal) );
}
.optimized :is(.horizontal, .vertical) {
--turnVal: calc( 90deg*var(--direction) );
}
.optimized :is(.horizontal, .applicate) {
--turnAxis: rotateY( var(--turnVal) );
}
.optimized .applicate {
background-color:blue;
--turnVal: calc( 90deg - 90deg*var(--direction) );
}
@keyframes spin { /* the details of the cube's spinning animation: */
100% { /* in the course of the animation, at the end of it... */
rotate: y 380deg; /* ...the cube will make a full 360 degree turn, adding to the the 20 degrees that it was already rotated */
}
}
/* OPTIONAL READING
This section details the appearance and the functioning of the play/pause toggler. It is not a part of the cube - but still a fun thing to learn if you're up for it.
*/
input {
display: none; /* hides the input, we'll have a label for it instead that is nicer than a tick-box */
}
.playBtn { /* that's the label */
background-image: linear-gradient(fuchsia 50%, teal 50%);
position: relative;
margin: 20em 40em;
width: 40em;
aspect-ratio: 1;
z-index: 1; /* places the label on a layer above the cube - as otherwise it'd be inside the 3D solid, and you wouldn't be able to interact with it */
display: grid; /* this label will have nested content, and we want it centered */
place-items: center;
transition: all .5s ease-in-out; /* when you interact with the button - hover, check/uncheck the input - it will change appearance. This makes it switch between each look smoothly rather than instantly */
--dist: calc(100%/6); /* gap between the two vertical bars of the pause button */
clip-path: polygon(0 0, 0 50%, 100% 50%, 100% 50%, 0 50%, 0 100%, 100% 50%, 100% 50%); /* cutour for the PLAY button*/
animation: hue 6s linear infinite paused;
}
.playBtn::before {
content: '';
position: absolute;
inset: 0;
background-color: lime;
opacity: 0;
transition: inherit;
}
.playBtn:hover { /* when you mouse over the label... */
scale: 1.25; /* ...it gets a little bit larger */
}
#play:not(:checked) ~ .scene .cube { /* if the PLAY button hasn't been pressed */
animation-play-state: paused; /* ...the animation is paued */
}
#play:checked ~ .playBtn { /* when you hit the PLAY button */
rotate: -90deg; /* ...it turns to its side */
clip-path: polygon(0 0, 0 calc(50% - var(--dist)), 100% calc(50% - var(--dist)), 100% calc(50% + var(--dist)), 0 calc(50% + var(--dist)), 0 100%, 100% 100%, 100% 0);
animation-play-state: running;
}
#play:not(:checked) ~ .playBtn::before {
opacity: 1; /* makes a splash of white color cover the split gradient color of the parent (the two bars of the pause button) */
}
@keyframes hue {
100% {
filter: hue-rotate(1turn); /* the button changes color as the cubes turn */
}
}
@media (orientation: portrait) { /* for mobile screens displaying in portait mode (a tall, narrow rectangle) */
body {
font-size: .25vh; /* the font size is now depended on the viewport height - rather than width - since now it's the narrower of the two dimensions */
flex-direction: column; /* all children are arranged in a vertical stack */
}
.scene:nth-of-type(1) {
perspective-origin: 50% 100%; /* moving the perspective point to the middle of the bottom line so that it's mirrored by the one below, set to the middle of the TOP edge */
}
.scene:nth-of-type(2) {
scale: 1 1; /* in mobile mode, we don't need a horizontal inversion/mirroring, that'd only cause confusion */
perspective-origin: 50% 0;
}
}
// This pen is used for demonstration in my tutorial on 3D CSS cubes at https://mackfitz.hashnode.dev/css-3d-the-cube-part-2
This Pen doesn't use any external CSS resources.
This Pen doesn't use any external JavaScript resources.