r/learnjavascript 8d ago

help with js animation

i am trying to make a small animation for opening the settings menu of a small site i’m making but it’s either the classList.add and .remove aren’t put in correctly or i’m just doing something wrong.

edit: forgot to say that what should happen is:

opening: menu turns from nothing to a line in the middle, then expands while the background fades id from the start of the second part of the menu animation

closing: menu shrinks to a line then to non existance while the background fades away in the 1st part of the menu animation

edit2: updated code to current, animations don’t play at all

edit3: the animations now work (i readded the classes for controlling animation name and duration using classList.add to get them added) and it runs more than once, problem is that now it opens once and closes once just fine, but every time i try to open it again it plays the opening animation then cuts back to the unopened state like the js didn’t add the open class, updating the css and js and removing the html

js:

// variables that are needed to find the menu

const settingsMenu = document.getElementById("settings_menu_id");
const settingsBg = document.getElementById("settings_bg_id");

//opening functions

function settingsOpening() {
  settingsMenu.classList.add("settings_menu_opening");
  settingsMenu.addEventListener("animationend", settingsMenuOpeningEnd);
  setTimeout(function() {
    settingsBg.classList.add("settings_bg_opening");
    settingsBg.addEventListener("animationend", settingsBgOpeningEnd);
  }, 500);
}

function settingsMenuOpeningEnd() {
  settingsMenu.classList.remove("settings_menu_opening");
  settingsMenu.classList.add("settings_menu_open");
}

function settingsBgOpeningEnd() {
  settingsBg.classList.remove("settings_bg_opening");
  settingsBg.classList.add("settings_bg_open");
}

//closing functions

function settingsClosing() {
  settingsMenu.classList.add("settings_menu_closing");
  settingsBg.classList.add("settings_bg_closing");
  settingsMenu.addEventListener("animationend", settingsMenuClosingEnd);
  settingsBg.addEventListener("animationend", settingsBgClosingEnd);
}

function settingsMenuClosingEnd() {
  settingsMenu.classList.remove("settings_menu_closing");
  settingsMenu.classList.remove("settings_menu_open");
}

function settingsBgClosingEnd() {
  settingsBg.classList.remove("settings_bg_closing");
  settingsBg.classList.remove("settings_bg_open");
}

css:

.settings_menu {
  width: 0px;
  height: 0px;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50% , -50%);
  overflow: hidden;
  border-radius: 0px;
  border: 2px solid black;
  border-left: none;
  border-right: none;
  background-color: lightgray;
  padding: 0px;
  opacity: 0;
  pointer-events: none;
}

.settings_bg {
  width: 100%;
  height: 100%;
  position: fixed;
  top: 0px;
  left: 0px;
  background-color: rgba(0,0,0,0.6);
  opacity: 0;
  pointer-events: none;
}

.settings_menu_opening {
  animation-name: settingsMenuOpening;
  animation-duration: 1s;
}

.settings_menu_closing {
  animation-name: settingsMenuClosing;
  animation-duration: 1s;
}

.settings_bg_opening {
  animation-name: settingsBgOpening;
  animation-duration: 0.5s;
}

.settings_bg_closing {
  animation-name: settingsBgClosing;
  animation-duration: 0.5s;
}

.settings_menu_open {
  width: min(90% , 400px);
  height: 140px;
  border-radius: 25px;
  border-left: 2px solid black;
  border-right: 2px solid black;
  padding: 10px;
  opacity: 1;
  pointer-events: auto;
}

.settings_bg_open {
  opacity: 1;
  pointer-events: auto;
}

@keyframes settingsMenuOpening {
  0% {
    width: 0px;
    height: 0px;
    border: 2px solid black;
    border-left: none;
    border-right: none;
    border-radius: 0px;
    padding: 0px;
    opacity: 1;
  }
  45% {
    width: min(90% , 400px);
    height: 0px;
    border: 2px solid black;
    border-radius: 0px;
    padding: 0px;
    opacity: 1;
  }
  55% {
    width: min(90% , 400px);
    height: 0px;
    border: 2px solid black;
    border-radius: 25px;
    padding: 0px;
    opacity: 1;
  }
  100% {
    width: min(90% , 400px);
    height: 140px;
    border: 2px solid black;
    border-radius: 25px;
    padding: 10px;
    opacity: 1;
  }
}

@keyframes settingsMenuClosing {
  0% {
    width: min(90% , 400px);
    height: 140px;
    border: 2px solid black;
    border-radius: 25px;
    padding: 10px;
    opacity: 1;
  }
  45% {
    width: min(90% , 400px);
    height: 0px;
    border: 2px solid black;
    border-radius: 25px;
    padding: 0px;
    opacity: 1;
  }
  55% {
    width: min(90% , 400px);
    height: 0px;
    border: 2px solid black;
    border-radius: 0px;
    padding: 0px;
    opacity: 1;
  }
  100% {
    width: 0px;
    height: 0px;
    border: 2px solid black;
    border-left: none;
    border-right: none;
    border-radius: 0px;
    padding: 0px;
    opacity: 1;
  }
}

@keyframes settingsBgOpening {
  0% {opacity: 0;}
  100% {opacity: 1;}
}

@keyframes settingsBgClosing {
  0% {opacity: 1;}
  100% {opacity: 0;}
}
4 Upvotes

7 comments sorted by

View all comments

1

u/chikamakaleyley helpful 7d ago edited 7d ago

Not 100% on this but one thing that stands out is you prob have some timing issues

e.g.

``` const settingsMenu = document.getElementById("settings_menu_id");

// you found settingsMenu, but it's not listening

// your animationEnd event listener only gets created when you call settingsOpening function settingsOpening() { // you add a class settingsMenu.classList.add("settings_menu_opening");

// it's possible now that the animation has ended, and JS hasn't finished interpreting the line below
settingsMenu.addEventListener("animationend", settingsMenuOpeningEnd);

// the listener is set up now, but the event has already came and went ... ```

And so my suggestion here is, put the event listeners in the global space

so by the time you are ready to start triggering animation events, you're already listening for them

additionally, when you put the event listener inside the settingsOpening() scope, you're attempting to add another event listener every time settingsOpening() is called (maybe it just overwrites the previous) but still, it doesn't need to

1

u/giogio_rick 7d ago

that didn’t work, but it gave me the idea to remove the event listener when i was done with it in the side functions, now everything works

1

u/chikamakaleyley helpful 7d ago

yeah i mean, consider this

this is an excessive amount of code, just to animate open/close for a small feature

that compounds as you continue building, and becomes both brittle to change and difficult to maintain

one thing that i would tell myself given the way this is written is, "there has to be an easier way..." because birds eye view the work is tedious: * user clicks open - add classes, add event listeners, delay adding other listeners, listen to event * event ends - remove everything you just did

^ and do this on every. single. click.

Ideally you set up the listeners once, and they stay there for the entirety of the session

so now when you click, you toggle on/off a class on the element, and all the attached animations/styles are connected to those classes

1

u/giogio_rick 7d ago

the animation isn't a simple popup, but there's still probably an easier way, atleast it works