0

I want a div to appear and disappear with an animation.

I found this question but the answer does not work for me.

Here is (not) working example.

import * as React from "react";
import "./styles.css";

export default function App() {
  const [show, setShow] = React.useState(false);
  return (
    <div className="bg-gray-500">
      <button onClick={() => setShow(!show)}>toggle display</button>
      {show && (
        <div className="absolute inset-x-0 top-0 origin-top-right transform p-2 transition duration-1000 ease-out">
          this should animate but it doesn't
        </div>
      )}
    </div>
  );
}

Any ideas?

tmaihoff
  • 2,867
  • 1
  • 18
  • 40
  • Your not working example is working for me – Wayne Mar 05 '23 at 23:33
  • Just for clarification - the button is working, the appearing div just does not animate. Does the text appear with an animation for you? If so, what browser do you use? My chrome does not animate this. – tmaihoff Mar 05 '23 at 23:57
  • The problem is with `show`. If it is false the element is removed from the render. You may use `visible` `invisible` – Nijat Mursali Mar 06 '23 at 00:15
  • Thanks for your answer @MattCarlotta . That makes sense. I tried your code snippet but unfortunately it doesn't work. I have update the example, could you please have a look? https://codesandbox.io/s/react-tailwind-playground-forked-3ifez5?file=/src/App.tsx – tmaihoff Mar 06 '23 at 00:16
  • @tmaihoff I forgot that transitions between `display:none` and `display:absolute` also won't work. You could control the opacity or visibility; but, that will still allow the element to take up visible space. In this case, you may want to handle animation states manually (utilizing react state) OR utilize something like [react-transition-group](https://www.npmjs.com/package/react-transition-group), which provides animation states for you. – Matt Carlotta Mar 06 '23 at 00:21
  • Here's an example using r-t-g, which pretty much accomplishes what you want: https://codesandbox.io/s/csstransition-component-m77l2vp00x OR if you're feeling up for a challenge, you can tap into [animation events](https://developer.mozilla.org/en-US/docs/Web/API/Element/animationend_event) with react state. – Matt Carlotta Mar 06 '23 at 00:27
  • many thanks for the example! However, I just got it working with the following: `
    {showText ? 'Auf die Wunschliste' : ''}
    ` You also need to set the following to `tailwind.config.js`: `module.exports = { theme: { extend: { transitionProperty: { width: "width" } } },`
    – tmaihoff Mar 06 '23 at 00:53
  • @tmaihoff Here's a simplified example using animation states: https://codesandbox.io/s/react-animation-states-zkhfjo. As a whole, animating elements that are added and removed from the DOM tends to result in a poor UX (UI interactions must be disabled; otherwise, the state can become out of sync with the animation). – Matt Carlotta Mar 06 '23 at 02:03

1 Answers1

0

In addition to customizing your TailwindCSS theme to include custom transitions, you can also add custom animations. Below is working snippet that adds a custom "slideIn" animation.

To add your own custom animation, extend the theme with the animation keyframes and set the animation properties. Optionally, you can use a CSS custom property (e.g., var(--delay, 0)) for the animation delay so that you can set the delay value individually for each item in your list.

The classname for the custom animation is the animation name in your theme, prefixed with animate-.

I created a staggered effect by increasing the delay slightly using the custom property for each item (e.g., style={{ "--delay": i * 0.25 + "s" }}).

function WishList({ list }) {
  const [show, setShow] = React.useState(false);
  const handleClick = () => setShow((prevState) => !prevState);

  return (
    <React.Fragment>
      <div className="flex align-items-center justify-between">
        <h2 className="my-3 text-3xl font-bold tracking-tight text-gray-900">
          WishList
        </h2>
        <button
          onClick={handleClick}
          className="my-3 rounded-md bg-indigo-600 py-2 px-3 text-sm font-semibold text-white hover:bg-indigo-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-indigo-600"
        >
          Toggle List
        </button>
      </div>
      {show && (
        <ul>
          {list.map((item, i) => (
            <li
              key={i}
              className="animate-slideIn opacity-0 text-xl my-2"
              style={{ "--delay": i * 0.25 + "s" }}
            >
              {item}
            </li>
          ))}
        </ul>
      )}
    </React.Fragment>
  );
}

function App(){
  return (
    <main className="container mx-auto p-4">
      <WishList list={[
        "Wish List Item 1",
        "Wish List Item 2",
        "Wish List Item 3",
        "Wish List Item 4",
        "Wish List Item 5"
      ]} 
    />
    </main>
  )
}

const rootElement = document.getElementById("root");
const root = ReactDOM.createRoot(rootElement);
root.render(<App />);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<script>
  tailwind.config = {
    theme: {
      extend: {
        keyframes: {
          slideIn: {
            "0%": { opacity: 0, transform: "translateX(100%)" },
            "100%": { opacity: 1, transform: "translateX(0)" }
          }
        },
        animation: {
          slideIn: "slideIn .25s ease-in-out forwards var(--delay, 0)"
        }
      }
    }
  };
</script>
<div id="root"></div>
jme11
  • 17,134
  • 2
  • 38
  • 48