Intro
Hello fellow devs, this is the 2nd part of the Recreate Spotify series! In this part I will recreate the top bar / header of the open.spotify.com.
As always, if you think that I could do anything differently feel free to leave a comment 🙂.
The process
The first step is to browse the homepage and understand how the top bar 'reacts' to the user's (my) actions.
From my browsing I discovered that the top bar:
- is always fixed on the top of the screen
- has 2 buttons and a dropdown
- when I scrolled the page, the background opacity changed gradually from transparent to opaque
Cutting the design to smaller pieces
After the browsing the next step is to cut the design to smaller pieces.
Looking at the top bar we can see that it consists of 2 main components
Those are the following:
- Arrow buttons
- Dropdown
The Arrow buttons component has 2 children (which have the same design):
- Left Arrow Button
- Right Arrow Button
The Dropdown has a hidden component that was not visible previously, but can be seen in the following image:
Now looking at the Dropdown reveals its children:
- The title (user icon, username, arrow down icon)
- Dropdown options (the list with 2 list items)
Time to code 💻
If you want to follow along, you can find the code from the part 1 of the series in this Github gist
Arrow Buttons
Starting from the arrow buttons I add them inside the header div
<div class="header">
<div class="header--buttons">
<button class="header--button previous">
<i class="lni-chevron-left"></i>
</button>
<button class="header--button next">
<i class="lni-chevron-right"></i>
</button>
</div>
</div>
Now let's add some css to the arrow buttons:
.header--button {
background: rgb(7, 7, 7);
color: #fff;
border: 0;
border-radius: 50%;
height: 34px;
width: 34px;
cursor: pointer;
outline: 0;
}
I need to add some distance between them, cause they are too close together.
.header--button.previous {
margin-right: 10px;
}
Dropdown
Time to add the dropdown
<div class="dropdown">
<button class="dropdown--button">
<span class="user-icon">
<i class="lni-user"></i>
</span>
<span class="text-bold">
username
</span>
<span>
<i class="lni-chevron-down"></i>
</span>
</button>
</div>
Styling the dropdown
.dropdown {
position: relative;
}
.dropdown--button {
outline: 0;
border: 0;
color: #fff;
border-radius: 20px;
display: flex;
justify-content: space-between;
align-items: center;
background-color: rgb(45, 45, 45);
padding: 6px 14px 6px 8px;
font-size: 0.75rem;
line-height: 0.75rem;
cursor: pointer;
user-select: none;
box-shadow: 0px 3px 3px 1px rgba(0, 0, 0, 0.5);
}
.header .dropdown--button span:not(:last-child) {
margin-right: 10px;
}
.header .dropdown--button .user-icon {
font-size: 1.3rem;
}
Adding a hover effect on the dropdown and its items
The effect is subtle and is easy to achieve, I just change the color and background-color to a brighter one.
.dropdown--button:hover,
.dropdown.open .dropdown--content li:hover {
background-color: rgb(49, 49, 49);
color: #fff;
}
Now I will vertically align the items inside the top bar and then I will add space between the Arrows Buttons and the Dropdown.
.header {
display: flex;
justify-content: space-between;
align-items: center;
}
If you want to sharpen your flexbox knowledge you can:
- read the css-tricks guide
- play a game called Flexbox Froggy
Dropdown items
Adding the dropdown items to the dropdown
<div class="dropdown">
<button class="dropdown--button">
<span class="user-icon">
<i class="lni-user"></i>
</span>
<span class="text-bold">
username
</span>
<span>
<i class="lni-chevron-down"></i>
</span>
</button>
<ul class="dropdown--content">
<li>Account</li>
<li>Logout</li>
</ul>
</div>
We can see that it needs some work 😅.
Adding some basic styles to the dropdown--content
.dropdown--content {
position: absolute;
list-style: none;
right: 0;
margin: 10px 0 0 0;
padding: 0;
width: 160px;
background-color: rgb(40, 40, 40);
border-radius: 5px;
box-shadow: 0px 3px 3px 1px rgba(0, 0, 0, 0.5);
}
and some styles to the dropdown list items
.dropdown .dropdown--content li {
padding-left: 40px;
font-size: 0.9rem;
border-bottom: 1px solid rgb(64, 64, 64);
color: #b3b3b3;
user-select: none;
height: 40px;
line-height: 40px;
}
.dropdown .dropdown--content li:first-child {
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
.dropdown .dropdown--content li:last-child {
border-bottom: 0;
border-bottom-left-radius: 5px;
border-bottom-right-radius: 5px;
}
I have finished with the Dropdown styles, but I need to add the toggling funcionality now.
Let's start with hiding the dropdown items.
.dropdown--content {
display: none;
}
The next step is to create a class that is called open and when the dropdown has that class then its contents will show.
.dropdown.open .dropdown--content {
display: block;
}
Having fun with Javascript
We add the below code just above the closing body tag.
I have added comments to explain what the code is doing.
- Run the code when the page is loaded
- When the user clicks on anywhere on the page then close all the open dropdowns
- Get all the dropdowns on the page
-
Add a click event listener to run when the user clicks on a dropdown
a. Find the .dropdown element
b. toggle the .open class on the element
<script>
document.addEventListener("DOMContentLoaded",() => {
/* Catch user click event on anywhere on the page */
document.querySelector('body').addEventListener('click', e => {
/* Close all the dropdowns */
document.querySelectorAll('.dropdown.open').forEach(item => {
item.classList.remove('open');
});
});
/* Get all the dropdowns on the page */
document.querySelectorAll('.dropdown').forEach(item => {
/*
When user clicks on a dropdown => then toggle the .open class.
.open class is responsible for opening / closing a dropdown
*/
item.addEventListener('click', e => {
e.preventDefault();
e.stopPropagation();
/* Find the closest dropdown */
const dropdown = e.target.closest('.dropdown');
if(!dropdown) return;
if (dropdown.classList.contains('open')) {
dropdown.classList.remove('open');
} else {
dropdown.classList.add('open');
}
});
});
});
</script>
With the above code I have achieved the following functionality:
Overview of the page
Scroll to change the top bar's background opacity
First, I will delete the background-color property of the .header div.
The .header css styles:
.header {
position: fixed;
top: 0;
left: 230px; /* sidebar width */
right: 0;
height: 60px;
padding-left: 24px;
padding-right: 24px;
display: flex;
justify-content: space-between;
align-items: center;
z-index:1;
}
Then, I will add the div element .header--bg inside the previous header element.
The code should look like the following:
<div class="header">
<div class="header--bg"></div>
<div class="header--buttons">
<button class="header--button previous">
<i class="lni-chevron-left"></i>
</button>
<button class="header--button next">
<i class="lni-chevron-right"></i>
</button>
</div>
<div class="dropdown">
<button class="dropdown--button">
<span class="user-icon">
<i class="lni-user"></i>
</span>
<span class="text-bold">
username
</span>
<span>
<i class="lni-chevron-down"></i>
</span>
</button>
<ul class="dropdown--content">
<li>Account</li>
<li>Logout</li>
</ul>
</div>
</div>
After that, I will give background-color to the new element and make it span the whole top bar's width.
.header--bg {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
opacity: 0;
background-color: rgb(18, 18, 18);
z-index: 0;
}
For testing purposes I add a dummy div inside the main element and give it a height of 200vh.
The code should look like this:
<main>
<div style="min-height: 200vh"></div>
</main>
Now all I need is to change the opacity of the header--bg element depending on the scroll position.
Having fun with Javascript (again)
It's a simple script that will change the opacity of the header--bg element depending on the user's scrolling position.
If the user scrolls more than opaqueThreshold = 2 * (the height of the top bar) then make the background fully opaque (opacity: 1).
While the user aproaches the opaqueThreshold the opacity becomes bigger.
const headerHeight = document.querySelector('.header').offsetHeight;
const opaqueThreshold = (headerHeight * 2);
const _main_el = document.querySelector('main');
_main_el.addEventListener('scroll', (e) => {
/*
Get the vertical scroll position of the 'main' element
*/
const scrollTop = _main_el.scrollTop;
const _header_bg_el = document.querySelector('.header--bg');
/*
Set the opacity of the top bar
depending on the current scroll position
of the main element
*/
const opacity = scrollTop / opaqueThreshold;
if(opacity > 1) opacity = 1;
_header_bg_el.style.opacity = opacity;
});
With the above code the area should look like the following:
<script>
document.addEventListener("DOMContentLoaded",() => {
/* Catch user click event on anywhere on the page */
document.querySelector('body').addEventListener('click', e => {
/* Close all the dropdowns */
document.querySelectorAll('.dropdown.open').forEach(item => {
item.classList.remove('open');
});
});
/* Get all the dropdowns in the page */
document.querySelectorAll('.dropdown').forEach(item => {
/*
When user clicks on a dropdown => then toggle the .open class.
.open class is responsible for opening / closing a dropdown
*/
item.addEventListener('click', e => {
e.preventDefault();
e.stopPropagation();
/* Find the closest dropdown */
const dropdown = e.target.closest('.dropdown');
if(!dropdown) return;
if (dropdown.classList.contains('open')) {
dropdown.classList.remove('open');
} else {
dropdown.classList.add('open');
}
});
});
const headerHeight = document.querySelector('.header').offsetHeight;
const opaqueThreshold = (headerHeight * 2);
const _main_el = document.querySelector('main');
_main_el.addEventListener('scroll', (e) => {
/* Get the vertical scroll position of the 'main' element */
const scrollTop = _main_el.scrollTop;
const _header_bg_el = document.querySelector('.header--bg');
/*
Set the opacity of the top bar
depending on the current scroll position
of the main element
*/
const opacity = scrollTop / opaqueThreshold;
if(opacity > 1) opacity = 1;
_header_bg_el.style.opacity = opacity;
});
});
</script>
Bonus 👏
Toggling the menu items on the left sidebar to 'active' when they are clicked.
/* Get all the left sidebar's menu items */
document.querySelectorAll('.main-menu .menu--item').forEach(item => {
/*
When user clicks on a menu item => then toggle the .active class.
.active class is responsible for adding the background to the menu--item
*/
item.addEventListener('click', e => {
e.preventDefault();
const menu_item = e.target.closest('.menu--item');
if(!menu_item) return;
/* Remove all the active menu--items */
document.querySelectorAll('.main-menu .menu--item').forEach(item => {
item.classList.remove('active');
});
/* "Activate" the click menu--item */
menu_item.classList.add('active');
});
});
Conclusion
That's all for the second part of the Recreate Spotify Series.
🎉 Thank you for reading through all the post! 🎉
If you have any questions or any feedback, let me know in the comments.
On the next part of the series I will create the Spotify Search page, which you can see below
You can find all the code from the series so far in this Github gist.
Top comments (0)