Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Animation for react-select menu open/close

Is it possible to animate opening and closing the menu? The menu node is removed or attached to DOM tree on opening/closing so this interferes with css animation. Is there a work around for this?

like image 735
Dmitriy Movchan Avatar asked Aug 21 '20 07:08

Dmitriy Movchan


2 Answers

For opening animation, it's relatively easy, you only have to add the animation to the <Menu/> component using css.

The following example use fade-in/out animation

@keyframes fadeIn {
  0% {
    opacity: 0;
    transform: translateY(2rem);
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}

.menu {
  animation: fadeIn 0.2s ease-in-out;
}

In your render method

<Select
  {...}
  components={{
    Menu: (props) => <components.Menu {...props} className="menu" />
  }}
/>

The closing animation is a bit trickier because when the close event fires, the menu element is removed immediately, leaving no time for the closing animation to run.

We need to change the close event behavior a little bit. The strategy is to just let the menu close abruptly as normal. But before that, we make another clone of the menu, and that clone will run the closing animation and remove itself when the animation finished.

// generate unique ID for every Select components
const [uniqueId] = React.useState(
  () => 'select_' + Math.random().toFixed(5).slice(2),
);

return (
  <Select
    id={uniqueId}
    onMenuClose={() => {
      const menuEl = document.querySelector(`#${uniqueId} .menu`);
      const containerEl = menuEl?.parentElement;
      const clonedMenuEl = menuEl?.cloneNode(true);

      if (!clonedMenuEl) return; // safeguard

      clonedMenuEl.classList.add("menu--close");
      clonedMenuEl.addEventListener("animationend", () => {
        containerEl?.removeChild(clonedMenuEl);
      });

      containerEl?.appendChild(clonedMenuEl!);
    }}
    {...}
  />
);

Don't forget to attach the closing animation to the right css class. In this case menu--close

@keyframes fadeOut {
  0% {
    opacity: 1;
    transform: translateY(0);
  }
  100% {
    opacity: 0;
    transform: translateY(2rem);
  }
}

.menu--close {
  animation: fadeOut 0.2s ease-in-out;
}

In the end, you will have something like this

enter image description here

Live Demo

Edit React-select Open/Close Animation

like image 141
NearHuscarl Avatar answered Oct 16 '22 07:10

NearHuscarl


Another way is to use menuIsOpen props instead of cloning.

const [isMenuOpen, setIsMenuOpen] = useState(false)
 
const openMenuHandler = async () => {
  await setIsMenuOpen(true)

  const menu = document.querySelector(`#select .menu`)     

  menu.style.opacity = '1'
}

const closeMenuHandler = () => {
  const menu = document.querySelector(`#select .menu`)

  menu.style.opacity = '0'

  setTimeout(() => {
    setIsMenuOpen(false)
  }, 400)
}   

<Select
  menuIsOpen={isMenuOpen}
  onMenuOpen={() => {openMenuHandler()}}
  onMenuClose={() => {closeMenuHandler()}}
/>
like image 42
Daniil Kirienko Avatar answered Oct 16 '22 09:10

Daniil Kirienko