Recreate: Spotify (part 2)
Tags: beginners, css, ui, javascript
Published: 2020-02-26 20:35:55

# 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](https://open.spotify.com).

If you have not followed the series this is the UI that I want to recreate.
![Spotify Homepage](https://dev-to-uploads.s3.amazonaws.com/i/zhyt80ryycqcfw5csktb.png)
<figcaption>1. Spotify Homepage</figcaption>
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](https://dev-to-uploads.s3.amazonaws.com/i/fwc6o9e9zcmt46le258x.gif)
<figcaption>2. The result of part 2 of the series</figcaption>

# 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](https://dev-to-uploads.s3.amazonaws.com/i/3x0h9lys380mqqbb0vor.png)
<figcaption>3. Cutting the top bar to smaller sections</figcaption>
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](https://dev-to-uploads.s3.amazonaws.com/i/kfwijsxg3o64kknfgugs.png)
<figcaption>4. Dropdown opened</figcaption>
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](https://gist.github.com/tsanak/9fdb7f8d956a758ca68a5d0f5cc6f07c)

### Arrow Buttons
Starting from the arrow buttons I add them inside the header div
```html
<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](https://dev-to-uploads.s3.amazonaws.com/i/z6uxjwuf2hob05dgej91.png)
<figcaption>5. Buttons added without styles</figcaption>
Now let's add some css to the arrow buttons:
```css
.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](https://dev-to-uploads.s3.amazonaws.com/i/zd8vcokqlaok3ybn9mnm.png)
<figcaption>6. Buttons with styles</figcaption>
I need to add some distance between them, cause they are too close together.
```css
.header--button.previous {
    margin-right: 10px;
}
```
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/zhue9ac56s8zz769gthr.png)
<figcaption>7. Distancing the buttons</figcaption>
### Dropdown
Time to add the dropdown
```html
<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](https://dev-to-uploads.s3.amazonaws.com/i/0umvlrrb8bsl47ctaz60.png)
<figcaption>8. Dropdown without styles</figcaption>
Styling the dropdown
```css
.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](https://dev-to-uploads.s3.amazonaws.com/i/wgct67db04wgdmo1awcv.png)
<figcaption>8. Dropdown with styles</figcaption>
Adding a hover effect on the dropdown and its items

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/z0cjfu6d139kvzpltv4w.gif)
<figcaption>9. Dropdown hover effect</figcaption>
The effect is subtle and is easy to achieve, I just change the color and background-color to a brighter one.
```css
.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__.
```css
.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](https://css-tricks.com/snippets/css/a-guide-to-flexbox/)
- play a game called [Flexbox Froggy](https://flexboxfroggy.com/)

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](https://dev-to-uploads.s3.amazonaws.com/i/a5y06atircqbnnufwsm7.png)
<figcaption>10. Top bar with correct spacing</figcaption>
### Dropdown items
Adding the dropdown items to the dropdown
```html
<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](https://dev-to-uploads.s3.amazonaws.com/i/poil1wal7emvnexi6xq6.png)
<figcaption>11. Dropdown add items with no styles</figcaption>
We can see that it needs some work 😅.

Adding some basic styles to the dropdown--content
```css
.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](https://dev-to-uploads.s3.amazonaws.com/i/howkat8ff3bvv9vflwcf.png)
<figcaption>12. Dropdown style the items container</figcaption>
and some styles to the dropdown list items
```css
.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](https://dev-to-uploads.s3.amazonaws.com/i/gmxv5zsc02v1z1kheqyc.png)
<figcaption>13. Dropdown style the items</figcaption>
I have finished with the __Dropdown__ styles, but I need to add the toggling funcionality now.
Let's start with hiding the dropdown items.
```css
.dropdown--content {
    display: none;
}
```
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/yilpdsgwn7ddv3akjp8u.png)
<figcaption>14. Dropdown hide the items</figcaption>
The next step is to create a class that is called _open_ and when the dropdown has that class then its contents will show.
```css
.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

```javascript
<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](https://dev-to-uploads.s3.amazonaws.com/i/v9cesjxeo8dzzdg03me5.gif)
<figcaption>15. Click dropdown to show the items</figcaption>
### Overview of the page
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/vw7w15ujwkmvdeiwjp2w.gif)
<figcaption>16. Spotify top bar finished</figcaption>
### 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:
```css
.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:
```html
<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.

```css
.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:
```html
<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](https://dev-to-uploads.s3.amazonaws.com/i/u8orclmarhz25nu6gpjk.gif)
<figcaption>17. Header always transparent</figcaption>
### 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.

```javascript
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 __<script>__ area should look like the following:

```javascript
    <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](https://dev-to-uploads.s3.amazonaws.com/i/fwc6o9e9zcmt46le258x.gif)
<figcaption>18. The result of part 2 of the series</figcaption>

## Bonus 👏
Toggling the menu items on the left sidebar to 'active' when they are clicked.

```javascript
/* 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](https://dev-to-uploads.s3.amazonaws.com/i/ubp7ipseh8eu1li6852g.gif)
<figcaption>19. Header gradually changing the opacity from 0 to 1</figcaption>

# 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](https://dev-to-uploads.s3.amazonaws.com/i/kk2i0v2pjq3unmqa5lkm.png)
<figcaption>20. Spotify Search page</figcaption>


You can find all the code from the series so far in [this Github gist](https://gist.github.com/tsanak/0ea7df18c4db04c4f1e37105562b56a7).