DEV Community

Cover image for Recreate: Spotify (part 2)
tsanak
tsanak

Posted on

Recreate: Spotify (part 2)

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.

If you have not followed the series this is the UI that I want to recreate.
Spotify Homepage

1. Spotify Homepage

As always, if you think that I could do anything differently feel free to leave a comment 🙂.

A sneak peek of the result:
Alt Text

2. The result of part 2 of the series

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.

Alt Text

3. Cutting the top bar to smaller sections

Looking at the top bar we can see that it consists of 2 main components

Those are the following:

  1. Arrow buttons
  2. Dropdown

The Arrow buttons component has 2 children (which have the same design):

  1. Left Arrow Button
  2. Right Arrow Button

The Dropdown has a hidden component that was not visible previously, but can be seen in the following image:

Alt Text

4. Dropdown opened

Now looking at the Dropdown reveals its children:

  1. The title (user icon, username, arrow down icon)
  2. 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>

The output from the above code is the following:
Alt Text

5. Buttons added without styles

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;
}

After the changes the buttons look like the Spotify header ones
Alt Text

6. Buttons with styles

I need to add some distance between them, cause they are too close together.

.header--button.previous {
    margin-right: 10px;
}

Alt Text

7. Distancing the buttons

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>

Alt Text

8. Dropdown without styles

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;
}

Alt Text

8. Dropdown with styles

Adding a hover effect on the dropdown and its items

Alt Text

9. Dropdown hover effect

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:

The above solution will make the div with the arrows go to the left of the top bar and the dropdown to the right.
Alt Text

10. Top bar with correct spacing

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>

Alt Text

11. Dropdown add items with no styles

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);
}

Alt Text

12. Dropdown style the items container

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;
}

Alt Text

13. Dropdown style the items

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;
}

Alt Text

14. Dropdown hide the items

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.

  1. Run the code when the page is loaded
  2. When the user clicks on anywhere on the page then close all the open dropdowns
  3. Get all the dropdowns on the page
  4. 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:

Alt Text

15. Click dropdown to show the items

Overview of the page

Alt Text

16. Spotify top bar finished

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.

Alt Text

17. Header always transparent

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>

The top bar now behaves similarly to the Spotify's web app and you can see it in the image below.
Alt Text

18. The result of part 2 of the series

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');
    });
});

Alt Text

19. Header gradually changing the opacity from 0 to 1

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

Alt Text

20. Spotify Search page

You can find all the code from the series so far in this Github gist.

Top comments (0)