DEV Community

loading...

How I built an animated reusable React modal.

anisbouzahar profile image Anis ・4 min read

Every web developer at some point of his/her career had to create a modal to display important dialogs to users,it might sound like a challenging task, the good news is it's not as difficult as it might seem to be 🙆‍♀️ ,today I will walk you through how to build an animated modal using Framer motion and styled-components.

demo

Prerequisites

- Basic understanding of react components.

lets start by describing the anatomy of a modal.

Alt Text
An Overlay is basically a layer over the app it takes full width and height of the window and it stops all interactions with the app .
Modal container is a container that encloses the content.
Close button button that would dismiss the modal.
content is what you actually want to show inside the modal.


Now that we defined the structure of the modal lets create our Modal.js file and install styled-components package.
at this point you might be wondering what is this famous styled-components

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.

now that styled-components is installed lets import the required libraries and define the component structure and style.

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

const Overlay=styled.div`
position:fixed;
    top:0;
    left:0;
    width:100%;
    height:100%;
    background:rgba(0,0,0,0.3);
`
const ModalContainer = styled.div`
    width:50%;
    height:50%;
    background-color: white;
    position:absolute;               // ----.
    top:50%;                         //     |positioning the container
    left:50%;                        //     |in the middle
    transform:translate(-50%,-50%);  //  ----.
border-radius:12px`

const CloseButton = styled.svg`
    width:20px;
    height:20px;
    position: absolute;
    right:18px;
    top:18px;
    cursor:pointer;
`


const Modal = ({ handleClose, children, isOpen }) => {
  if (isOpen) {
    return (
      <Overlay>
        <ModalContainer>
          <CloseButton
            onClick={handleClose}
            xmlns="http://www.w3.org/2000/svg"
            viewBox="0 0 20.39 20.39"
          >
            <title>close</title>
            <line
              x1="19.39"
              y1="19.39"
              x2="1"
              y2="1"
              fill="none"
              stroke="#5c3aff"
              strokeLinecap="round"
              strokeMiterlimit="10"
              strokeWidth="2"
            />
            <line
              x1="1"
              y1="19.39"
              x2="19.39"
              y2="1"
              fill="none"
              stroke="#5c3aff"
              strokeLinecap="round"
              strokeMiterlimit="10"
              strokeWidth="2"
            />
          </CloseButton>
          {children}
        </ModalContainer>
      </Overlay>
    );
  } else {
    return <></>;
  }
};

export default Modal

Thanks to the styled components we created our structure with CSS inside the JS file, notice that we have isOpen prop to show and hide the modal and handle close to to dismiss the modal.

Time to dive into Framer motion and create an animated button to open the modal
create an other file name it OpenModalButton.js

import React from "react";
import { motion } from "framer-motion";
import styled from "styled-components";

const OpenModalButton = styled(motion.button)`
  font-size: 1.2rem;
  padding: 20px;
  border-radius: 50px;
  border: none;
  background-color: #5c3aff;
  color: white;
`;
const animatedOpenButton = ({ children }) => {
  return (
    <OpenModalButton whileHover={{ scale: 1.1 }} whileTap={{ scale: 0.9 }}>
      {children}
    </OpenModalButton>
  );
};

export default animatedOpenButton;



I will be importing both components in App.js for demonstration purpose and define event handelers.

import React, { useState } from "react";
import "./styles.css";
import Modal from "./Modal";
import OpenModalButton from "./OpenModalButton";

export default function App() {
  const [isOpen, toggle] = useState(false);

  function handlOpenModal(open) {
    console.log("close modal");
    toggle(open);
  }

  return (
    <div className="App">
      <OpenModalButton handlClick={() => handlOpenModal(true)}>
        Open modal
      </OpenModalButton>
      <Modal isOpen={isOpen} handleClose={() => handlOpenModal(false)} />
    </div>
  );
}

Here comes the fun part .... Animation!!!! 😀

to do that we need to migrate some of the modal components to framer motion components.
lets start with the Overlay we want it to transition from:

   initial state          open                upon exit
   opacity :0 ===========> opacity :1;=======>opacity:0
import {motion,AnimatePresence} from 'framer-motion'

const Overlay = styled(motion.div)`
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.3);
  cursor: pointer;
`;
const Modal = ({ handleClose, children, isOpen }) => {
  return(
  <AnimatePresence>
      {isOpen &&
      <Overlay initial={{opacity:0}} animate={{opacity:1}} exit={{opacity:0}}>
     /*.....*/
      </Overlay>
      }
  </AnimatePresence>

    );

};

export default Modal;

I wrapped the modal inside AnimatePresence which helps animate a component before being removed using exit attribute.
Time to orchestrate the overlay animation with the container using variants
we will animate the container from :
top :'-50%' to top:'50%'
with transition type spring to give it a bouncy feel to it.
lets declare our variants:

/*...*/

const modalVariant = {
  initial: { opacity: 0 },
  isOpen: { opacity: 1 },
  exit: { opacity: 0 }
};
const containerVariant = {
  initial: { top: "-50%", transition: { type: "spring" } },
  isOpen: { top: "50%" },
  exit: { top: "-50%" }
};
const Modal = ({ handleClose, children, isOpen }) => {
  return (
    <AnimatePresence>
      {isOpen && (
        <Overlay
          initial={"initial"}
          animate={"isOpen"}
          exit={"exit"}
          variants={modalVariant}
        >
          <ModalContainer variants={containerVariant}>
            /*...*/
          </ModalContainer>
        </Overlay>
      )}
    </AnimatePresence>
  );
};

export default Modal;

we are finally done you can check the code source.

Edit react animated modal.

Discussion (1)

pic
Editor guide
Collapse
mugnimaestra profile image
Muhammad Mugni Hadi

Hey, this is very cool, thank you for sharing! I've been looking for a way to use css animation with React way