30 days of JavaScript - Part 5 of 5

January 11, 2020

I can’t believe I finally made it through the whole of JS30 Challenge. This is the last part: Day 26 through to 30.

Again, all my solutions are accessible on my CodePen, go and check it out in order to explore the context for some of these lessons in more detail.

Day 26: Stripe Follow Along Dropdown

This is one of the coolest lessons of this series I must admit. We have a navigation with the three menu items. Each item has its own dropdown (a separate element) that we hide with opacity: 0 and display: none. It will be invisible on the page until it gets an active class on mouseenter when the arrow white background element shows up.

The follow along white background is a completely separate element that resizes itself depending on where it us on the page and the content of each dropdown.

navigation

We start by grabbing all

  • elements from the navigation bar and add two event listeners to this nodeList:

            💥 mouseenter - handleEnter
            💥 mouseleave - handleLeave

    Whenever we hover over an li element, we want to find the .dropdown element and display it. We can do that by adding and removing classes and changing css accordingly. We want the content of the dropdown we hover over to show. For that we will change the css properties of .dropdown element. Both display will change from none to block and the value of opacity from 0 to 1.

    The dropdown element has been hidden with opacity: 0 and display: none. Although hiding with display none could be enough, we want for it to get shown on trigger-enter, and you cannot go from opacity 0 to 1 as well as from display none to block. So in order to make it work we will stage it with two separate classes:

                    .trigger-enter .dropdown {
                        display: block;
                    }

    If we changed the opacity here to 1, that would immediately show the dropdown on the page, but we want the transition to work. And so, we won’t add the opacity: 1 right away here, instead after that split second using the active class:

                    .trigger-enter-active .dropdown {
                        opacity: 1;
                    }

    On enter event, we add a class of trigger-enter to list item element (trigger). We also add trigger-enter-active class after 1.5 seconds in order for the follow-along div to work.

    On leave event, we remove both the classes.

    Cool ES6 arrow function scope tip here: if you do not want the scope of your this to be window, but instead you want to inherit it from the parent function, use the arrow functions as opposed to the regular js functions.

    The second part is adding the white background that resizes itself depending on the content. On enter, we add the class of open to the background element. And remove it on leave. In our css, we add:

                    .dropdownBackground .open {
                        opacity: 1;
                    }

    And when we hover over one of the li elements, we will see this element in the top left corner:

    background

    Now we want this background to follow our .dropdown element instead. For that we need to know its position on the page, height and width with the help of getBoundingClientRect function that gives us all the coordinates needed for this element. Once we’ve got that captured, we set style property of the background element to follow the li elements coordinates. One important thing to add here is to remember that there might be some other elements added before our navigation. Hence, we should also know its position on the page nad make sure to subtract top and left values to get the precise location of the li item on the side.

    Day 27: Click and Drag to Scroll

    This is another of very useful lessons. Before we go into the details of implementing the drag and scroll, I would like to focus your attention on a few CSS items that I personally needed a refresher on:

                💥justify-content: center;
                    using Flexbox, this property centers the items along the line
    
                💥align-items: center; 
                    items are centered in the cross-axis
    
                💥overflow-x: scroll;
                    it sets a scroll bar to show when content overflows a block-level element's left and right edges
    
                💥white-space: nowrap;
                    prevents the text from from wrapping when it exceeds the element's set width
    
                💥will-change: transform;
                    it lets the browser know which elements are about to be manipulated

    Another thing worth pointing out is how Wes managed to give the items this slightly elevated, 3D look.

    This is done with perspective 💥perspective: 500px; property on items element. If that is not applied, they are all flat, like so:

    flat items

    If each individual item element does not have 💥display: inline-flex; property. They will just stack on top of one another like so:

    items on top

    Knowing all that, we can proceed to learning about the implementation of drag and scroll.

    1. We need to know if we are clicking (let isDown) and if so, where that anchor point of clicking down is (let startX)
    2. If I move my mouse either to the left or right, we need to know how much pixels we scrolled either side to be able to move elements accordingly. Our initial position is let scrollLeft (scrollLeft = slider.scrollLeft;).

    We will use a combination of four events that we listen for on our items wrapper:

                    💥 mousedown => isDown = true;
                                    slider.classList.add('active');
    
                    💥 mouseleave => isDown = false;
                                    slider.classList.remove('active');
    
                    💥 mouseup => isDown = false;
                                slider.classList.remove('active');
    
                    💥 mousemove

    We add an ‘active’ css class when we touch on the items to add a visual effect of the cursor changed to 💥grabbing and 💥transform: scale(1), and otherwise we remove it.

    In order to record where the initial clickdown happened, we will use the mousedown event that gives us 💥pageX value of the X coordinates. A small gotcha here, we need to take into consideration any extra margins etc., and so we can do that by offsetting our slider and in this way we derive the anchor’s precise position within ‘items’ element:

                    startX = e.pageX - slider.offsetLeft;

    In order to figure out where the the initial scroll was within items element, we can use the scrollLeft property on the slider:

                    scrollLeft = slider.scrollLeft;

    Next thing we need to figure out is where the cursor is when we moved it. We need to recalculate it every time a user moves the mouse. Hence, we are utilizing mousemove event for that.

                    x = e.pageX - slider.offsetLeft;

    How far did we deviate from that initial click?

                    const walk = x - startX;

    Now we have the position of the div calculated, we should make our elements move accordingly on the page:

                    slider.scrollLeft = walk;

    However, if we left it like that, the user experience wouldn’t be the best, as the items element is a bit too jumpy. That is because we recalculate the scroll left every single time. The reason why we captured scrollLeft value when we did that initial click was so that we could reference it inside of this function:

                    slider.scrollLeft = scrollLeft -  walk;

    Plus if you would like your slider to move a little faster when you drag it, you can multiply the value of walk a few times:

                    const walk = (x - startX) * 3;

    That is it for this lesson! The code might not be too difficult, but there goes a lot of math into calculating every move on scroll.

    Day 28: Video Speed Controller UI

    In this lesson we are learning about how to create my favourite video/recording feature i.e. speedbar. You know when for example you cannot make it to the meeting, but have a recording available, and you save yourself 30mins of your life listening to it at 2x speed? 😏😏

    We have a video box and speed-bar element on the page right next to each other. As always, we start by grabbing all these elements (video, speed, speed-bar), so that we can manipulate them as needed.

    We will change the value of speed and it will affect the playback speed of the video. The range we will operate within(0-100%) is 0.4x to 4x.

    In order to change the speed’s value, we add mousemove event listener to the speed’s element. When we console log the event, we will see all the properties available to us:

    mousemove event

    We will use pageY value from which we need to subtract the offsetTop value of our speed element. That will give us where on the speed bar our mouse is currently. Then we convert it to percentage and we set the style of our bar’s height to that calculated height:

    speedbar

    Next thing is to update the playback rate. We calculate it by using the established earlier lower and upper bounds (min, max) as well as the percent. Once we’ve got the playback rate calculated, we can set the textContent of the bar to that value.

    Last thing to do is to set our 💥video.playbackRate to that value too.

    Beautiful! Off to the next one..