DEV Community

Cover image for How to make a horizontal sliding navigation
Brett Anda 🔥🧠 for Developer Bacon 🥓🥓

Posted on • Edited on • Originally published at developerbacon.ca

How to make a horizontal sliding navigation

This tutorial is about making the navigation menu from Developer Bacon. This article is aimed for people that don't know Vue.js and has been stripped out of this tutorial. There is the use of SCSS but there will be examples with both SCSS and the compiled CSS. The JavaScript for this tutorial use the library AnimeJs. If you want to just look at the code you can find it in the Codepen example below or continue this article for the examples.

See the Pen Sliding Navigation from Developer Bacon by Brett
(@brettanda) on CodePen.

The HTML

Let's get started with the HTML for the navigation menu. The HTML example has all the necessary tags and necessary for both the CSS and Js script to look the same as Developer Bacon. These can of course be changed to whatever you like (just make sure that you change it both in the CSS file and Js file).

<header class="header">
    <a class="logo-link" href="#">
        Developer Bacon
    </a>

    <nav class="nav">
        <a class="nav__link" href="#">Search</a>
        <a class="nav__link" href="#">About</a>
        <a class="nav__link" href="#">Contact</a>
    </nav>

    <div class="nav__background nav__background-1"></div>
    <div class="nav__background nav__background-2"></div>

    <button class="nav-ham toggleNav">
        <span class="nav-ham__line"></span>
        <span class="nav-ham__line"></span>
        <span class="nav-ham__line"></span>
    </button>

    <div class="close-nav toggleNav"></div>
</header>
Enter fullscreen mode Exit fullscreen mode

The SCSS and CSS

This first example is the SCSS for the navigation menu. If you are not familiar with SCSS the second example is the compiled SCSS to CSS.

// Based on this [snippet](https://css-tricks.com/snippets/sass/mixin-manage-breakpoints/) by Hugo Giraudel.
@mixin break($point) {
    @if map-has-key($breakpoints, $point) {
        @media screen and (max-width: map-get($breakpoints, $point)) {
            @content;
        }
    } @else {
        @warn 'Unfortunately, no value could be retrieved from `#{$breakpoint}`. '
            + 'Available breakpoints are: #{map-keys($breakpoints)}.';
    }
}

$breakpoints: (
    "sm": 800px,
    "md": 1000px
) !default;

.header,
.header * {
    box-sizing: border-box;
}

body {
    background-color: black;
}

nav[role="navigation"] {
    text-align: center;

    a {
        display: inline-block;
        margin: 1em 0.75em 2em;
    }
}

.header {
    background-color: red;
    display: flex;
    justify-content: space-between;
    margin-bottom: 20px;
    padding: 1rem;
    box-shadow: 0 2px 10px 1px rgba(0, 0, 0, 0.2);
}

img.logo-link {
    height: 1rem;
    width: auto;
}

.logo-link,
.nav__link {
    text-decoration: none;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: black;
    transition: color 0.3s ease;

    &:hover,
    &:focus {
        text-decoration: none;
        color: white;
    }
}

.nav__link {
    margin-left: 20px;
    display: inline;

    @include break(md) {
        padding: 0.75rem;
    }
}

.nav-ham,
.nav__background {
    display: none;
}

.nav-ham {
    z-index: 2;
}

@include break(md) {
    .nav {
        display: none;

        &.is-active {
            display: flex;
            flex-direction: column;
            padding: 8rem 2rem 70vh 1rem;
            justify-content: space-around;
            text-align: right;
            position: fixed;
            background-color: #222;
            color: white;
            top: 0;
            height: 100vh;
            width: 15rem;
            z-index: 5;
            box-shadow: 0 2px 10px 1px rgba(0, 0, 0, 0.2);

            a {
                color: white;
                border-color: white;
            }
        }
    }

    .nav__background-1,
    .nav__background-2 {
        &.is-active {
            display: block;
            z-index: 3;
            background-color: gray;
            box-shadow: 0 2px 10px 1px rgba(0, 0, 0, 0.2);
            top: 0;
            height: 100vh;
            width: 18rem;
            position: fixed;
        }
    }

    .nav-ham {
        display: block;
        background: none;
        border: none;
        outline: none;
        height: 3rem;
        width: 3rem;
        position: absolute;
        right: 1rem;
        top: 0;
        z-index: 6;

        .nav-ham__line {
            border: 1px solid white;
            height: 0;
            width: 2rem;
            display: block;
            margin: 0.6rem 0 0.6rem auto;
            transform-origin: center right;
            transition: 0.3s;
        }

        & .is-active {
            &:first-child {
                transform-origin: center right;
                transform: rotate(-45deg);
            }

            &:nth-of-type(2) {
                width: 0;
                opacity: 0;
            }

            &:last-child {
                transform-origin: center right;
                transform: rotate(45deg);
            }
        }
    }
}

.close-nav {
    display: none;
    position: fixed;
    right: -100vw;
    top: 0;
    background: rgba(0, 0, 0, 0.7);
    height: 100vh;
    width: 100vw;
    opacity: 0;
    z-index: 2;

    @include break(md) {
        &.is-active {
            display: block;
        }
    }
}

.logo {
    vertical-align: bottom;
    width: 33px;
    padding: 0 1px;
}
Enter fullscreen mode Exit fullscreen mode

The compiled CSS

This is the compiled CSS from the SCSS above.

.header,
.header * {
    box-sizing: border-box;
}

body {
    background-color: black;
}

nav[role="navigation"] {
    text-align: center;
}
nav[role="navigation"] a {
    display: inline-block;
    margin: 1em 0.75em 2em;
}

.header {
    background-color: red;
    display: -webkit-box;
    display: flex;
    -webkit-box-pack: justify;
    justify-content: space-between;
    margin-bottom: 20px;
    padding: 1rem;
    box-shadow: 0 2px 10px 1px rgba(0, 0, 0, 0.2);
}

img.logo-link {
    height: 1rem;
    width: auto;
}

.logo-link,
.nav__link {
    text-decoration: none;
    text-transform: uppercase;
    letter-spacing: 1px;
    color: black;
    -webkit-transition: color 0.3s ease;
    transition: color 0.3s ease;
}
.logo-link:hover,
.logo-link:focus,
.nav__link:hover,
.nav__link:focus {
    text-decoration: none;
    color: white;
}

.nav__link {
    margin-left: 20px;
    display: inline;
}
@media screen and (max-width: 1000px) {
    .nav__link {
        padding: 0.75rem;
    }
}

.nav-ham,
.nav__background {
    display: none;
}

.nav-ham {
    z-index: 2;
}

@media screen and (max-width: 1000px) {
    .nav {
        display: none;
    }
    .nav.is-active {
        display: -webkit-box;
        display: flex;
        -webkit-box-orient: vertical;
        -webkit-box-direction: normal;
        flex-direction: column;
        padding: 8rem 2rem 70vh 1rem;
        justify-content: space-around;
        text-align: right;
        position: fixed;
        background-color: #222;
        color: white;
        top: 0;
        height: 100vh;
        width: 15rem;
        z-index: 5;
        box-shadow: 0 2px 10px 1px rgba(0, 0, 0, 0.2);
    }
    .nav.is-active a {
        color: white;
        border-color: white;
    }

    .nav__background-1.is-active,
    .nav__background-2.is-active {
        display: block;
        z-index: 3;
        background-color: gray;
        box-shadow: 0 2px 10px 1px rgba(0, 0, 0, 0.2);
        top: 0;
        height: 100vh;
        width: 18rem;
        position: fixed;
    }

    .nav-ham {
        display: block;
        background: none;
        border: none;
        outline: none;
        height: 3rem;
        width: 3rem;
        position: absolute;
        right: 1rem;
        top: 0;
        z-index: 6;
    }
    .nav-ham .nav-ham__line {
        border: 1px solid white;
        height: 0;
        width: 2rem;
        display: block;
        margin: 0.6rem 0 0.6rem auto;
        -webkit-transform-origin: center right;
        transform-origin: center right;
        -webkit-transition: 0.3s;
        transition: 0.3s;
    }
    .nav-ham .is-active:first-child {
        -webkit-transform-origin: center right;
        transform-origin: center right;
        -webkit-transform: rotate(-45deg);
        transform: rotate(-45deg);
    }
    .nav-ham .is-active:nth-of-type(2) {
        width: 0;
        opacity: 0;
    }
    .nav-ham .is-active:last-child {
        -webkit-transform-origin: center right;
        transform-origin: center right;
        -webkit-transform: rotate(45deg);
        transform: rotate(45deg);
    }
}
.close-nav {
    display: none;
    position: fixed;
    right: -100vw;
    top: 0;
    background: rgba(0, 0, 0, 0.7);
    height: 100vh;
    width: 100vw;
    opacity: 0;
    z-index: 2;
}
@media screen and (max-width: 1000px) {
    .close-nav.is-active {
        display: block;
    }
}

.logo {
    vertical-align: bottom;
    width: 33px;
    padding: 0 1px;
}
Enter fullscreen mode Exit fullscreen mode

The magic JavaScript

Before we get into the Javascript of this project we should add AnimeJs to the project. To do this use this command in your project directory.

yarn add animejs
Enter fullscreen mode Exit fullscreen mode

I used AnimeJs because it is great for handleing animations with Javascript. The AnimeJs documentation can be found at animejs.com.

document.querySelectorAll(".toggleNav").forEach(item => {
    item.addEventListener("click", function() {
        console.log("clicked");
        toggleNav();
    });
});

function closeNav() {
    document.querySelectorAll(".nav-ham, .nav-ham span").forEach(function(item) {
        item.classList.remove("is-active");
    });
    anime({
        targets: ".nav",
        duration: 400,
        easing: "easeInOutSine",
        right: [0, "-15rem"],
        complete: function() {
            document.querySelector(".nav").classList.remove("is-active");
        }
    });

    anime({
        targets: ".nav__background",
        delay: 150,
        duration: 400,
        easing: "easeInOutSine",
        right: ["0rem", "-18rem"],
        complete: function() {
            document.querySelectorAll(".nav__background").forEach(item => {
                item.classList.remove("is-active");
            });
        }
    });

    anime({
        targets: ".close-nav",
        delay: 150,
        duration: 400,
        easing: "easeInOutSine",
        right: [0, "-100vw"],
        opacity: 0,
        complete: function() {
            document.querySelector(".close-nav").classList.remove("is-active");
        }
    });
}
function toggleNav() {
    document.querySelectorAll(".nav-ham, .nav-ham span").forEach(item => {
        if (item.classList.contains("is-active")) {
            item.classList.remove("is-active");
        } else {
            item.classList.add("is-active");
        }
    });

    if (document.querySelector(".nav").classList.contains("is-active")) {
        this.closeNav();
    } else {
        anime({
            targets: ".close-nav",
            duration: 400,
            easing: "easeInOutSine",
            right: ["-100vw", 0],
            opacity: 1,
            begin: function() {
                document.querySelector(".close-nav").classList.add("is-active");
            }
        });

        anime({
            targets: ".nav:not(.is-active)",
            duration: 400,
            delay: 150,
            easing: "easeInOutSine",
            right: ["-15rem", 0],
            begin: function() {
                document.querySelector(".nav").classList.add("is-active");
            }
        });

        anime({
            targets: ".nav__background:not(.is-active)",
            duration: 400,
            easing: "easeInOutSine",
            // right: ['-100vw',"-20vw"],
            right: ["-18rem", "0rem"],
            begin: function() {
                document.querySelectorAll(".nav__background").forEach(function(item) {
                    item.classList.add("is-active");
                });
            }
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)