Javascript Menu : Click on to extend & Click off to shorten

Viewed 32

I created a menu for a web project, but I have a problem with the javascript side. I would like to extend the div container if it's clicked. And vice versa, I would like to shorten it if I click everywhere except on it. Actually, the program is able to shorten and extend the div menu but I can't manage the events :

  • if div is small -> extend it by clicking on it
  • if div is big -> shorten it by clicking everywhere except on it

There is my program :

const menu = document.querySelector('.wrapper')
    const offClick = () => {
        menu.classList.toggle('active')
        document.removeEventListener('click', offClick)
    }
    const handleClick = (e) => {
        e.stopPropagation()
        menu.classList.toggle('.active')
        if (menu.classList.contains('.active')){
            document.addEventListener('click', offClick)
        }
    }
menu.addEventListener('click', handleClick)
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

 body{
   display: flex;
   justify-content: center;
   align-items: center;
   min-height: 100vh;
   background-color: #10131c;

 }

 .wrapper{
   position: absolute;
   width: 105px;
   height: 105px;
   background-color: #212532;
   border-radius: 10px;
   cursor: pointer;
   display: flex;
   justify-content: center;
   align-items: center;
   transition: 0.5s;
   transition-delay: 0.8s;

 }

 .wrapper.active{
  width: 300px;
  height: 300px;
  transition-delay: 0s;
 }

 .wrapper span{
   position: absolute;
   width: 10.5px;
   height: 10.5px;
   display: flex;
   justify-content: center;
   align-items: center;
   background-color: #fff;
   border-radius: 50%;
   transform: translate(calc(18px * var(--x)), calc(18px * var(--y)));
   transition: transform 0.5s, width 0.5s, height 0.5s;
   background-color: 0.5s;
   transition-delay: calc(0.1s * var(--i));
 }

 .wrapper.active span{
  width: 67.5px;
  height: 67.5px;
  background-color: #333849;
  transform: translate(calc(90px * var(--x)), calc(90px * var(--y)))

 }

 .wrapper span ion-icon{
  transition: 0.5s;
  font-size: 0em
}

.wrapper.active span ion-icon{
  font-size: 2.025em;
  color: #fff;
}

.wrapper.active span:hover ion-icon{
  color: #d33d32;
  filter: drop-shadow(0 0 3px #d33d32) 

}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSite</title>
    <link rel="stylesheet" href="styles/style.css" type="text/css">
</head>
<body class="body">
    <div class="wrapper">
        <span style = "--i:0;--x:-1;--y:0;"><ion-icon name="code-outline"></ion-icon></span>
        <span style = "--i:1;--x:1;--y:0;"><ion-icon name="moon-outline"></ion-icon></span>
        <span style = "--i:2;--x:0;--y:-1;"><ion-icon name="bulb-outline"></ion-icon></span>
        <span style = "--i:3;--x:0;--y:1;"><ion-icon name="chatbubbles-outline"></ion-icon></span>
        <span style = "--i:4;--x:1;--y:1;"><ion-icon name="duplicate-outline"></ion-icon></span>
        <span style = "--i:5;--x:-1;--y:-1;"><ion-icon name="duplicate-outline"></ion-icon></span>
        <span style = "--i:6;--x:0;--y:0;"><ion-icon name="ribbon-outline"></ion-icon></ion-icon></span>
        <span style = "--i:7;--x:-1;--y:1;"><ion-icon name="rocket-outline"></ion-icon></span>
        <span style = "--i:8;--x:1;--y:-1;"><ion-icon name="stats-chart-outline"></ion-icon></span>
    </div>
    
    <script type="module" src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.esm.js"></script>
    <script nomodule src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.js"></script>
    <script src="script/script.js"></script>
</body>
</html>

Thank you for your help !

2 Answers

First of all it looks really nice :)

As far as the javascript code you could keep it simple by adding the event listener on the whole body and checking the target of your click event has a parent with a 'wrapper' class, by using the closest method.

document.addEventListener('click',(event)=>{
    if(event.target.closest(".wrapper")){
        menu.classList.add('active')
    }else{
        menu.classList.remove('active')
    }
})

Firstly, note that amending event handlers within an event handler is normally a code smell. There is usually a much better way to do what you need to achieve.

In this case you need to attach each of your event handlers just the once. handleClick() should be attached to the .wrapper so that it expands when clicked, and offClick() should be attached to the document so that it contracts the .wrapper.

const menu = document.querySelector('.wrapper');
const offClick = () => menu.classList.remove('active');
const handleClick = (e) => {
  e.stopPropagation();
  menu.classList.add('active'); // or .toggle('active')
}

menu.addEventListener('click', handleClick);
document.addEventListener('click', offClick);
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  display: flex;
  justify-content: center;
  align-items: center;
  min-height: 100vh;
  background-color: #10131c;
}

.wrapper {
  position: absolute;
  width: 105px;
  height: 105px;
  background-color: #212532;
  border-radius: 10px;
  cursor: pointer;
  display: flex;
  justify-content: center;
  align-items: center;
  transition: 0.5s;
  transition-delay: 0.8s;
}

.wrapper.active {
  width: 300px;
  height: 300px;
  transition-delay: 0s;
}

.wrapper span {
  position: absolute;
  width: 10.5px;
  height: 10.5px;
  display: flex;
  justify-content: center;
  align-items: center;
  background-color: #fff;
  border-radius: 50%;
  transform: translate(calc(18px * var(--x)), calc(18px * var(--y)));
  transition: transform 0.5s, width 0.5s, height 0.5s;
  background-color: 0.5s;
  transition-delay: calc(0.1s * var(--i));
}

.wrapper.active span {
  width: 67.5px;
  height: 67.5px;
  background-color: #333849;
  transform: translate(calc(90px * var(--x)), calc(90px * var(--y)))
}

.wrapper span ion-icon {
  transition: 0.5s;
  font-size: 0em
}

.wrapper.active span ion-icon {
  font-size: 2.025em;
  color: #fff;
}

.wrapper.active span:hover ion-icon {
  color: #d33d32;
  filter: drop-shadow(0 0 3px #d33d32)
}
<body class="body">
  <div class="wrapper">
    <span style="--i:0;--x:-1;--y:0;"><ion-icon name="code-outline"></ion-icon></span>
    <span style="--i:1;--x:1;--y:0;"><ion-icon name="moon-outline"></ion-icon></span>
    <span style="--i:2;--x:0;--y:-1;"><ion-icon name="bulb-outline"></ion-icon></span>
    <span style="--i:3;--x:0;--y:1;"><ion-icon name="chatbubbles-outline"></ion-icon></span>
    <span style="--i:4;--x:1;--y:1;"><ion-icon name="duplicate-outline"></ion-icon></span>
    <span style="--i:5;--x:-1;--y:-1;"><ion-icon name="duplicate-outline"></ion-icon></span>
    <span style="--i:6;--x:0;--y:0;"><ion-icon name="ribbon-outline"></ion-icon></span>
    <span style="--i:7;--x:-1;--y:1;"><ion-icon name="rocket-outline"></ion-icon></span>
    <span style="--i:8;--x:1;--y:-1;"><ion-icon name="stats-chart-outline"></ion-icon></span>
  </div>

  <script type="module" src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.esm.js"></script>
  <script nomodule src="https://unpkg.com/ionicons@5.5.2/dist/ionicons/ionicons.js"></script>
</body>

Related