003 — The Pyramide
Suck Less at CSS November Challenge
This November, though ridiculously busy, I’ve set myself (and others) the challenge of practicing CSS, challenging CSS, with the plan of — in general — sucking less at it by the end of the month. If you’d like to participate, feel free to sign up here.
The Third challenge is replicating this image:
Again, deceptively simple (though not so much as Challenge 002), here the kicker is that it’s not just a matter of drawing the sun overlooking a pyramid, the sun animates across the sky and the pyramid’s shadow moves in conjunction with the sun’s motion. Additionally, the sun doesn’t move in a purely circular manner around a pivot point at the center of the image, it’s more of an oval.
I’m going to stick to what’s been working for me throughout these challenges and begin by breaking the challenge into a tasks list of much smaller problems which will be easier to tackle individually.
The tasks:
- Create the background square, with a box shadow.
- Create the center circle, in light blue.
- Create the ground in the center circle.
- Create the pyramid.
- Create the sun.
- Create the shadow.
- Animate the center circle fading in and out.
- Animate the sun travelling across the sky.
- Animate the pyramid’s shadow moving in sync with the motion of the sun.
Steps 1 and 2 — creating the background square and center circle
This part turned out to just be a rehash of the same thing I’ve been doing on every project, plus adding a circle to the center.
.container {
width: 400px;
height: 400px;
margin: 40px auto;
display: flex;
flex-direction: column;
justify-content: center;
background: #272C34; -webkit-box-shadow: 2px 3px 10px 0 #cccccc;
box-shadow: 2px 3px 10px 0 #cccccc; .tableau {
height: 200px;
width: 200px;
margin: 0 auto;
border-radius: 100%;
background: #7DDFFC;
}
}
Like before, I’m leaning on flexbox for layout, box-shadow, and not a whole lot else.
Steps 3 and 4 — create the ground and pyramid
And — something new [Clip Path] — take note that this isn’t really cross browser compatible, but I’m going to go with it anyway because it’s just so very cool. Basically, it lets me set a path on the main div and clip out everything outside that path. That way I can drop the ground, the pyramid, etc. into the main div and I don’t have to worry about rounding all the edges on everything, if it sticks out past the main circle I clip it doesn’t display. It’s magic and I love it! There’s a cool tool by Bennett Feely for building clip paths: here.
-webkit-clip-path: circle(100px at 100px 100px);
clip-path: circle(100px at 100px 100px);
The syntax for clipping out my center circle is as simple as adding those two lines to my .tableau
css from above, and it ensures that any elements I place inside the div with the tableau class will get clipped to fit inside the circle. Nifty!
Armed with that knowledge, I set the clip path for the main circle, added ground on the bottom of the circle, then used two divs for the pyramid, the first div, I used clip path to create the entire pyramid, then with the second, I made a second clip path to only cover half the first, set a different color on it, then used z-index to ensure it stacked and was visible. Note: to use z-index to make them stack, I needed to set the main area to flex, then the inner divs to position absolute.
.tableau {
display: flex;
height: 200px;
width: 200px;
margin: 0 auto;
border-radius: 100%;
background: #7ddffc;
-webkit-clip-path: circle(100px at 100px 100px);
.pyramid-left {
position: absolute;
margin-top: 60px;
margin-left: 40px;
height: 60px;
width: 120px;
background: #efefef;
clip-path: polygon(0% 100%, 100% 100%, 50% 0%);
}
.pyramid-right {
position: absolute;
z-index: 1;
margin-top: 60px;
margin-left: 100px;
height: 60px;
width: 60px;
background: #dddddd;
clip-path: polygon(30% 100%, 100% 100%, 0% 0%);
}
.ground {
position: absolute;
margin-top: 120px;
height: 79px;
width: 200px;
background: #d8c977;
}
}
Which results in:
Steps 5 and 6 — Create the Sun and Shadow
This part was just more of the same. I laid down another two divs, the one for the sun I colored yellow and used a border radius of 100% to make it circular, and for the shadow, I leaned on clip-path again.
.shadow {
position: absolute;
z-index: 1;
margin-top: 120px;
margin-left: 0px;
height: 30px;
width: 210px;
background: rgba(0,0,0,0.2);
clip-path: polygon(40px 0%, 160px 0%, 95% 80%);
}
.sun {
position: absolute;
width: 35px;
height: 35px;
border-radius: 100%;
background: #efef00;
margin-top: 25px;
margin-left: 25px;
z-index: 2;
}
Which brings us to here:
Now that all the main elements are in place, it’s time to move on to the animations.
Step 7 — Animate the center circle fading in and out
For the fade in / fade out, I used an animation to set the opacity:
.tableau {
...
animation: fade-out-image 5s 1s ease infinite;
}
@keyframes fade-out-image {
0% { opacity: 0; }
25% { opacity: .8; }
50% { opacity: 1; }
75% { opacity: .8; }
100% { opacity: 0; }
}
Setting it like this lets it ramp quickly from fully opaque to 80%, then more slowly to 100%, 5 seconds for the animation with a 1 second delay felt about right to me.
Step 8 — Animate the sun travelling across the sky
For this, I set another animation, which takes the color of the sun from orange to yellow and back again as it travels across the sky. I used a rotate transform in the keyframe to rotate the sun around an origin just off center in order to get a not quite circular path for the sun.
.sun {
...
transform-origin: 75px 175px;
animation: sun-across-sky 5s 1s ease infinite;
}
@keyframes sun-across-sky {
0% {transform: rotate(-55deg); background: orange;}
50% {transform: rotate(25deg); background: yellow;}
100% {transform: rotate(75deg); background: orange;}
}
The key takeways here are that I set the animation to have the same duration and delay, but modified the number of keyframes so that the sun would appear to be at the apex longer, and that in order to get a bit of an oblong path for the sun, I set the transform-origin so that it would pivot around a spot other than the center of the image.
Step 9 — Animate the pyramid’s shadow moving in sync with the motion of the sun
For this part, I just set keframes to match what I had for the sun and then set a new clip-pane for each step and put the animation on the shadow.
.shadow {
...
clip-path: polygon(40px 0%, 160px 0%, 95% 80%);
animation: pyramid-shadow 5s 1s ease infinite;
}
@keyframes pyramid-shadow {
0% { clip-path: polygon(40px 0%, 160px 0%, 85% 110%); }
50% { clip-path: polygon(40px 0%, 160px 0%, 45% 50%); }
100% { clip-path: polygon(40px 0%, 160px 0%, 5% 120%); }
}
And — then I was done. Well, I did pop back in and change my animations to use a bezier curve instead of ease.
cubic-bezier(0.785, 0.135, 0.150, 0.860)
I didn’t come up with those numbers myself, I googled for “css animiation bezier” and landed on https://matthewlein.com/tools/ceaser and tried a few then put a couple minor tweaks on the one I liked the most and used that.
You can see my code [here on CodePen].
Retrospective / notes on this challenge:
My final product didn’t end up looking quite as slick as the original. Once I was done, I went in and looked at the code from the original and he used more intricate transitions. I’ve decided “it’s good enough” for now. If I somehow manage to finish all 100 before November’s up, I’ll come back and refactor this one to be a bit more polished but, for now, I’m just going to be happy that I’ve got a rising and setting sun casting a shadow over a pyramid.
Concepts that I found myself leaning on:
- keyframes
- transitions
- transforms
- bezier curves
- clip-path
Onward and upward to the next challenge. This one was quite a bit tougher than the others to this point though it really did reinforce everything with animations from the last one.