physicsboy
physicsboy

Reputation: 6326

CSS for disappearing div animation is not pausing on hover

I am working on a snackbar that appears/disappears after x amount of time, and I would like the timer to be paused when I hover over the snackbar.

However, it seems that the way I have laid this out doesn't allow this to work, with the setTimeout and the useState...

I can't find much online about how this can be fixed that I can work out, so any help would be appreciated.

https://stackblitz.com/edit/react-dyq9mg

App.js

const App = () => {

  const [showSnack, setShowSnack] = useState(false);

  const toggle = () => {
    setShowSnack(true);
    setTimeout(() => {
      setShowSnack(false);
    }, 1000);
  }

  return (
    <div>
      <button onClick={toggle}>Toggle</button>
      <Snack show={showSnack} />
    </div>
  );
}

render(<App />, document.getElementById('root'));

Snack.js

const Snack = ({show}) => {

  return(
    <div className={`my-snack ${show ? 'show' : ''}`}>My Snackbar</div>
  );
}

index.css

.my-snack {
  position: fixed;
  width: 100%;
  height: 500px;
  background: lightgray;
  bottom: 0;
  visibility: hidden;
  cursor: pointer;
}

.my-snack.show {
  visibility: visible;
  animation: fadein 0.5s, fadeout 0.5s 9.6s;
  animation-play-state: running;
}

.my-snack:hover {
  animation-play-state: paused paused;
}

/*Animations to fade the snackbar in and out*/
@keyframes fadein {
  from {bottom: -500px;}
  to {bottom: 0;}
}

@keyframes fadeout {
  from {bottom: 0;}
  to {bottom: -500px; opacity: 0;}
}

Upvotes: 0

Views: 427

Answers (2)

Dennis Martinez
Dennis Martinez

Reputation: 6512

One way you could solve this is by using the onAnimationEnd event instead of a timer. I'd recommend this approach over a timer since you'd control all the timing through css and the component would react to when the animation has finished.

<Snack
  show={showSnack}
  onAnimationEnd={(e) => {
    setShowSnack(false)
  }}
/>

const Snack = ({ show, onAnimationEnd }) => {
  return (
    <div
      className={`my-snack ${show ? 'show' : ''}`}
      onAnimationEnd={(e) => {
        if (e.animationName === 'fadeout') {
          onAnimationEnd(e)
        }
      }}>
      My Snackbar
    </div>
  )
}

One note about this specific approach (for your use case) is you have to know the animation name in the js since you're using multiple animations at once. e.g. (e.animationName === 'fadeout')

Example: https://stackblitz.com/edit/react-szkq7w

Upvotes: 1

Thomas Upton
Thomas Upton

Reputation: 1899

I believe you might be conflating the CSS animation to fade in the snackbar with the showing and hiding of the snackbar itself. You could use onMouseOver and onMouseOut to pause and start the timer respectively. Of course, you'd have to pass these to the Snack component and set them on the DOM node.

  const timer = useRef(null);

  const clearSnackTimer = () => clearTimeout(timer.current);

  const startSnackTimer = () => timer.current = setTimeout(() => setShowSnack(false), 1000);

  const toggle = () => {
    setShowSnack(!showSnack);
    startSnackTimer();
  };

  return (
    <div>
      <button onClick={toggle}>Toggle</button>
      <Snack show={showSnack} onMouseOver={clearSnackTimer} onMouseOut={startSnackTimer} />
    </div>
  );

Something is still wonky with the fadeout transition, but I think this solves for the requirement to pause hiding the snackbar hiding on hover, if that is your intention.

Upvotes: 1

Related Questions