Menu and sun/moon animated icons with CSS
As part of my site’s CSS cleanup, I removed the Font Awesome icon font. I was loading too much extraneous CSS for only a handful of icons. In some cases, like the GitHub icons, I removed them entirely. However, I wanted to keep a few of my icons. The menu icon is important to the opening and closing of my site's sidebar. The sun and moon icon indicates the presence of a dark mode.
I wanted to give the new icons some fun animations when they're clicked. While I could've tried animating the SVG icons, I decided to create the icons with pure CSS instead.
Menu icon
I wanted the menu icon to animate between states when the sidebar is opened or closed. Some sites animate between the hamburger menu and an "x", others between the menu and an arrow. I played around with animating an arrow, but as I was experimenting I ended up with a "-" that I like much more. Basically a minimize icon.
The page's default state is with the sidebar menu open and the "-" icon. This is easily created with the ::before
and ::after
pseudo-elements.
.menu_btn_icon {
position: absolute;
width: 20px;
height: 2px;
border-radius: 2px;
background: var(--header);
transition: transform 0.75s ease-out, var(--page-color-transition);
}
.menu_btn_icon::before,
.menu_btn_icon::after {
position: absolute;
content: '';
width: 20px;
height: 2px;
border-radius: 2px;
background: var(--header);
transition: transform 0.5s ease-out, var(--page-color-transition);
}
I've given the main .menu_btn_icon
a transform duration of 0.75 seconds, and the ::before
and ::after
elements a transform duration of 0.5 seconds. This gives the icon a little extra interest as it animates, with the center line moving a little slower than the top and bottom lines, and the entire icon rotating for that full 0.75 seconds. Remember that the pseudo-elements will also be affected by the rotation of the parent element.
When the icon is clicked and the sidebar-hidden
class is added to the page body, the top line moves up 8 pixels and the bottom line moves down 8 pixels. Both of those lines rotate clockwise 180 degrees. The center line rotates counterclockwise 180 degrees, as does the entire rest of the icon. I feel like this makes the icon feel like it's folding up upon itself.
.sidebar-hidden .menu_btn_icon {
transform: rotate(-180deg);
}
.sidebar-hidden .menu_btn_icon::before {
transform: translateY(-8px) rotate(180deg);
}
.sidebar-hidden .menu_btn_icon::after {
transform: translateY(8px) rotate(180deg);
}
Just changing the rotation of the .menu_btn_icon
from -180 degrees to 180 degrees changes the entire feel of the icon.
Sun and moon icon
The sun and moon icons were a bit trickier and I'm still not sure that I'm satisfied with them. However, I plan to add more themes soon, so I may end up removing or changing the icon anyway.
This icon uses two different color themes. The body will be given the data-theme attribute of "dark" when the sun icon is displayed. We can easily add these two color themes with CSS variables.
:root {
--header: #4a4e69;
--background: #f4f3f2;
--page-color-transition: background .2s ease, color .2s ease;
}
[data-theme="dark"] {
--header: #c9ada7;
--background: #11112a;
}
The concept for this icon was that the moon would morph into the sun on click. The moon is pretty easy to make. It's a circle with another background color circle covering it to form a crescent shape.
.darkmode_icon {
position: absolute;
width: 20px;
height: 20px;
border-radius: 10px;
background: var(--header);
transform-origin: center center;
transition: transform 0.75s ease-in-out, var(--page-color-transition);
}
.darkmode_icon::after {
position: absolute;
content: '';
width: 16px;
height: 16px;
left: 8px;
bottom: 4px;
border-radius: 10px;
background: var(--background);
transform-origin: center center;
transition: transform 0.5s ease, left 0.25s ease, var(--page-color-transition);
}
However, the sun needs some extra elements for each of its rays. I've added eight child elements to the .darkmode_icon
element.
<div class="darkmode_icon">
<span class="ray"></span>
<span class="ray"></span>
<span class="ray"></span>
<span class="ray"></span>
<span class="ray"></span>
<span class="ray"></span>
<span class="ray"></span>
<span class="ray"></span>
</div>
Each of these are placed in the center of the icon, hidden by the moon and its ::after
pseudo-element. They're also given an initial rotation so they pop from the inside of the sun when it animates instead of spinning.
.darkmode_icon .ray {
position: absolute;
left: 7px;
top: 7px;
width: 6px;
height: 6px;
border-radius: 6px;
background: var(--header);
transform-origin: center;
transition: transform 0.5s ease-in-out, var(--page-color-transition);
}
.ray:nth-child(1) {
transform: rotate(45deg) translateX(0);
}
.ray:nth-child(2) {
transform: rotate(90deg) translateX(0);
}
.ray:nth-child(3) {
transform: rotate(135deg) translateX(0);
}
.ray:nth-child(4) {
transform: rotate(180deg) translateX(0);
}
.ray:nth-child(5) {
transform: rotate(225deg) translateX(0);
}
.ray:nth-child(6) {
transform: rotate(270deg) translateX(0);
}
.ray:nth-child(7) {
transform: rotate(315deg) translateX(0);
}
.ray:nth-child(8) {
transform: rotate(360deg) translateX(0);
}
When the icon is clicked, the "dark" data-attribute is added to the body element of the page. I've added an attribute selector to detect this attribute. When this happens, the center circle scales down, the masking element on the moon scales to 0 and moves off to the right, and each of the sun rays move outward from the middle of the icon. If you look at the .darkmode_icon
css above, you'll see that it has a transform duration of 0.75 seconds. All of the other elements transform in 0.5 seconds. This makes the scaling of the icon lag a little bit behind the rest of the animation.
[data-theme="dark"] {
& .darkmode_icon {
transform: scale(0.6);
}
& .darkmode_icon::after {
left: 15px;
bottom: 8px;
transform: scale(0);
}
& .ray:nth-child(1) {
transform: rotate(45deg) translateX(-16px);
}
& .ray:nth-child(2) {
transform: rotate(90deg) translateX(-16px);
}
& .ray:nth-child(3) {
transform: rotate(135deg) translateX(-16px);
}
& .ray:nth-child(4) {
transform: rotate(180deg) translateX(-16px);
}
& .ray:nth-child(5) {
transform: rotate(225deg) translateX(-16px);
}
& .ray:nth-child(6) {
transform: rotate(270deg) translateX(-16px);
}
& .ray:nth-child(7) {
transform: rotate(315deg) translateX(-16px);
}
& .ray:nth-child(8) {
transform: rotate(360deg) translateX(-16px);
}
}
I like these changes. I think it gives the website a little more interest. I may try animating some SVG icons next, if I have the need for additional icons. As I said above, I'm planning on some different CSS themes in the future, and I'd like each to have its own special icon.