DEV Community

Nuno Pereira
Nuno Pereira

Posted on

Your very first responsive and animated navigation bar with React and React-Spring

In this tutorial you will build a simple, responsive and animated navigation bar with React JS. You will use styled-components for the css and react-springs for the animations.

You can check here the final result of this project.

Styled components is a CSS-in-JS styling framework that uses tagged template literals in JavaScript and the awesome power of CSS to provide a platform that allows you to write actual CSS to style react components. In essence, styled components are easy-to-make react components you write with the styled-components library where you can style your components with plain CSS inside your javascript code and React-Spring is a spring-physics based animation library that should cover most of your UI related animation needs in a very simple way.

Lets start !

I will assume that you have node installed on your computer, you can check it by typing in your terminal :

node -v
Enter fullscreen mode Exit fullscreen mode

if you don’t please install the latest version.

OK, all set now ! You will start a new project using create-react-app so open your terminal and type:

npx create-react-app react-navbar
Enter fullscreen mode Exit fullscreen mode

And add the dependencies you will need during the project

npm install styled-components react-spring
Enter fullscreen mode Exit fullscreen mode

Now open your newly created project with your favorite code editor, go to the src folder and delete app.css, index.css and logo.svg.

Open your index.js file and remove the line :

import ./index.css
Enter fullscreen mode Exit fullscreen mode

Also open your app.js and clean it up to look like this:

import React from react;

const App = () =>  {
  return(
    <div>

    </div>
  )
}

export default App;
Enter fullscreen mode Exit fullscreen mode

In your src folder create a directory named styles with a Global.js file inside it, also in your src folder create a components folder, a sub-folder named navbar and inside this folder create this files: Brand.js , BurgerMenu.js, CollapseMenu.js and Navbar.js.

We will also create a folder named assets in the src folder and we will put a image inside of it, you can download the file from here.

Your project tree should look like this:

Project Tree

Great, this will be your starting point.

First of all your going to create the global style file using Styled Components, open your Global.js file and add this code:

import { createGlobalStyle } from 'styled-components';

const GlobalStyles = createGlobalStyle`

  @import url('https://fonts.googleapis.com/css?family=Montserrat:400,600&display=swap');;

  *,
  *::after,
  *::before {
    margin: 0px;
    padding: 0px;
    box-sizing: inherit;
  }

  html {
    font-size: 62.5%;
  }

  body {
    box-sizing: border-box;
    font-family: 'Montserrat', sans-serif;
  }
`;

export default GlobalStyles;
Enter fullscreen mode Exit fullscreen mode

Now you will open your App.js file, refactor it to a class component, add the dependencies we will need, add the Global.js and Navbar.js components and the state for the navbar.

import React, { Component } from 'react'
import Navbar from "./components/navbar/Navbar";

import GlobalStyle from './styles/Global';

class App extends Component {
  state = {
    navbarOpen: false
  }

  handleNavbar = () => {
    this.setState({ navbarOpen: !this.state.navbarOpen });
  }

  render() {

    return (
      <>
        <Navbar 
          navbarState={this.state.navbarOpen} 
          handleNavbar={this.handleNavbar}
        />
        <GlobalStyle />
      </>
    )
  }
}

export default App
Enter fullscreen mode Exit fullscreen mode

Great, before you start working on the Navbar.js component you will deal with the small components, open your Brand.js and make it look like this:

import React from 'react'
import styled from "styled-components";

import logo from "../../assets/logo-min.png";

const Brand = () => {
  return (
    <Image src={logo} alt="Company Logo" />
  )
}

export default Brand

const Image = styled.img`
  height: 85%;
  margin: auto 0;
`;
Enter fullscreen mode Exit fullscreen mode

Now the BurgerMenu.js:

import React from 'react';
import styled from "styled-components";

const Burgermenu = (props) => {
  return (
    <Wrapper onClick={props.handleNavbar}>
      <div className={ props.navbarState ? "open" : "" }>
        <span>&nbsp;</span>
        <span>&nbsp;</span>
        <span>&nbsp;</span>
      </div>
    </Wrapper>
  );
}

export default Burgermenu;

const Wrapper = styled.div`
  position: relative;
  padding-top: .7rem;
  cursor: pointer;
  display: block;

  & span {
    background: #fdcb6e;
    display: block;
    position: relative;
    width: 3.5rem;
    height: .4rem;
    margin-bottom: .7rem;
    transition: all ease-in-out 0.2s;
  }

  .open span:nth-child(2) {
      opacity: 0;
    }

  .open span:nth-child(3) {
    transform: rotate(45deg);
    top: -11px;
  }

  .open span:nth-child(1) {
    transform: rotate(-45deg);
    top: 11px;
  }

`;
Enter fullscreen mode Exit fullscreen mode

Great, now the final to components, first the CollapseMenu.js:

import React from 'react';
import styled from 'styled-components';

import { useSpring, animated } from 'react-spring';

const CollapseMenu = (props) => {
  const { open } = useSpring({ open: props.navbarState ? 0 : 1 });

  if (props.navbarState === true) {
    return (
      <CollapseWrapper style={{
        transform: open.interpolate({
          range: [0, 0.2, 0.3, 1],
          output: [0, -20, 0, -200],
        }).interpolate(openValue => `translate3d(0, ${openValue}px, 0`),
      }}
      >
        <NavLinks>
          <li><a href="/" onClick={props.handleNavbar}>link n1</a></li>
          <li><a href="/" onClick={props.handleNavbar}>link n2</a></li>
          <li><a href="/" onClick={props.handleNavbar}>link n3</a></li>
          <li><a href="/" onClick={props.handleNavbar}>link n4</a></li>
        </NavLinks>
      </CollapseWrapper>
    );
  }
  return null;
};

export default CollapseMenu;

const CollapseWrapper = styled(animated.div)`
  background: #2d3436;
  position: fixed;
  top: 4.5rem;
  left: 0;
  right: 0;
`;

const NavLinks = styled.ul`
  list-style-type: none;
  padding: 2rem 1rem 2rem 2rem;

  & li {
    transition: all 300ms linear 0s;
  }

  & a {
    font-size: 1.4rem;
    line-height: 2;
    color: #dfe6e9;
    text-transform: uppercase;
    text-decoration: none;
    cursor: pointer;

    &:hover {
      color: #fdcb6e;
      border-bottom: 1px solid #fdcb6e;
    }
  }
`;
Enter fullscreen mode Exit fullscreen mode

And finally the Navbar.js;

import React from 'react'
import styled from "styled-components";
import { useSpring, animated, config } from "react-spring";

import Brand from "./Brand";
import BurgerMenu from "./BurgerMenu";
import CollapseMenu from "./CollapseMenu";

const Navbar = (props) => {
  const barAnimation = useSpring({
    from: { transform: 'translate3d(0, -10rem, 0)' },
    transform: 'translate3d(0, 0, 0)',
  });

  const linkAnimation = useSpring({
    from: { transform: 'translate3d(0, 30px, 0)', opacity: 0 },
    to: { transform: 'translate3d(0, 0, 0)', opacity: 1 },
    delay: 800,
    config: config.wobbly,
  });

  return (
    <>
      <NavBar style={barAnimation}>
        <FlexContainer>
          <Brand />
          <NavLinks style={linkAnimation}>
            <a href="/">link n1</a>
            <a href="/">link n2</a>
            <a href="/">link n3</a>
            <a href="/">link n4</a>
          </NavLinks>
          <BurgerWrapper>
            <BurgerMenu
              navbarState={props.navbarState} 
              handleNavbar={props.handleNavbar}
            />
          </BurgerWrapper>
        </FlexContainer>
      </NavBar>
      <CollapseMenu 
        navbarState={props.navbarState} 
        handleNavbar={props.handleNavbar}
      />
   </>
  )
}

export default Navbar

const NavBar = styled(animated.nav)`
  position: fixed;
  width: 100%;
  top: 0;
  left: 0;
  background: #2d3436;
  z-index: 1;
  font-size: 1.4rem;
`;

const FlexContainer = styled.div`
  max-width: 120rem;
  display: flex;
  margin: auto;
  padding: 0 2rem;;
  justify-content: space-between;
  height: 5rem;
`;

const NavLinks = styled(animated.ul)`
  justify-self: end;
  list-style-type: none;
  margin: auto 0;

  & a {
    color: #dfe6e9;
    text-transform: uppercase;
    font-weight: 600;
    border-bottom: 1px solid transparent;
    margin: 0 1.5rem;
    transition: all 300ms linear 0s;
    text-decoration: none;
    cursor: pointer;

    &:hover {
      color: #fdcb6e;
      border-bottom: 1px solid #fdcb6e;
    }

    @media (max-width: 768px) {
      display: none;
    }
  }
`;

const BurgerWrapper = styled.div`
  margin: auto 0;

  @media (min-width: 769px) {
    display: none;
  }
`;
Enter fullscreen mode Exit fullscreen mode

And you are done !

If you don't want to write all this code you can fork the github repo I made for this example.

See you soon for more tips and tutorials !

Top comments (19)

Collapse
 
matthill8286 profile image
matthill8286

You need to stop relying upon hoisting as all of your examples would throw an eslint error. For example, you are calling CollapseWrapper before it has been declared and this is consistent throughout your code unless you are actually hoisting functions by intent declare the pointer first.

Collapse
 
nunocpnp profile image
Nuno Pereira

This is a standard way of use Styled Components in React but thanks for your comment. If you dont link to use it this way you can put the styled component on another file

Collapse
 
matthill8286 profile image
matthill8286

My comment was purely about your javascript. The code you have given us as an example would fail when run through eslint.

Please be careful sharing code that is bad practice as you are encouraging others to write code in this way.

Thread Thread
 
nunocpnp profile image
Nuno Pereira

I understand what you are trying to say but, is not true.

You will not get any linting error, if you quickly install eslint and extend airbnb preset or even react-standard for instance on my demo you will see that no error or warning will show up.

Declaring styled-components in react at the end of the file is not consider bad practice either.

Collapse
 
dance2die profile image
Sung M. Kim

Thanks for the nice series of posts on DEV, Nuno~

May I request to add syntax highlights for code snippets?
Check out the Editor Guide for more info.

Collapse
 
nunocpnp profile image
Nuno Pereira

Thx for the tip ! I'll do it on all my articles !

Collapse
 
dance2die profile image
Sung M. Kim

Thank you ~

Collapse
 
shameera91 profile image
Shameera Anuranga

great post

Collapse
 
nunocpnp profile image
Nuno Pereira

Thank you !!

Collapse
 
thepedroferrari profile image
Pedro Ferrari

Thank you for the amazing, simple and straightforward. I was looking into spring and your post really helped :)

Collapse
 
nunocpnp profile image
Nuno Pereira

Thank you so much for your positive feedback !

Collapse
 
adhibhuta profile image
Nature

Thank you so much, I am a beginner this is exactly what I was looking for, simple and straightforward.

Collapse
 
nunocpnp profile image
Nuno Pereira

Im glad I could help you ! Thx for the feedback !

Collapse
 
farooqkrkt profile image
farooqkrkt

thanks for the tutorial. can we make navbar font color & background color different for different links?

Collapse
 
nunocpnp profile image
Nuno Pereira

sure you can, just style the a tags accordingly

Collapse
 
markdreed512 profile image
Mark Reed

Thanks for the tut! I'm just curious why you would need to refactor to an App.js class component?

Collapse
 
nunocpnp profile image
Nuno Pereira

Today with hooks you dont need it, but when I made the tutorial i still used class based react components

Collapse
 
vevi1 profile image
vevi1

thanks for this wonderful tutorials. I have an observation which is Module not found: Can't resolve './styles/Global'.....this is the error i get, thanks.

Collapse
 
nunocpnp profile image
Nuno Pereira

Thanks for your feedback !

Did you create de globalstyles file ?

You tried to follow the tutorial or you get that error cloning the repository ?