Mitya
Mitya

Reputation: 34596

CSS animation - revert on mouseout

I'm sure this must have been asked before and I've found related questions but I can't quite seem to crack this.

I have an element which receives a class and, on doing so, expands. Later, when that class is removed, it should revert (animate) back to its original width.

let el = document.querySelector('#side-bar');
el.addEventListener('click', evt => el.classList.toggle('contracted'));
#side-bar {
  height: 100%;
  width: 75px;
  background: red;
  position: fixed;
  left: 0;
  top: 0;
}

#side-bar.contracted {
  animation: .5s side-bar-contract forwards;
}

#side-bar:not(.contracted) {
  animation: .5s side-bar-expand forwards;
}

@keyframes side-bar-expand {
  to {
    width: 350px;
  }
}

@keyframes side-bar-contract {
  to {
    width: 75px;
  }
}
<div id='side-bar' class='contracted'></div>

The expansion animation works fine. But the reversion animation doesn't happen; it just snaps back to its original properties, no anim.

Fiddle

What am I doing wrong?

[ EDIT ]

OK I should obviously have mentioned why I'm not doing this with transition. This is part of a wider set of dependent animations which run in a sequence, one after another. My understanding is that this sort of chronologically non-trivial situation is better for animation rather than transition.

Upvotes: 1

Views: 117

Answers (3)

Gildas.Tambo
Gildas.Tambo

Reputation: 22653

UPDATE: (Removing the animation at the beginning)

let init = 0,
    el = document.querySelector('#side-bar');

el.addEventListener('click', function() {
    if (init < 1) {
        init++;
        el.classList.remove("init");
        el.classList.add('contracted');
    }
    el.classList.toggle('contracted');

});
#side-bar {
	height: 100%;
	width: 75px;
	background: #d4653c;
	position: fixed;
	left: 0;
	top: 0;
	padding: .8rem;
}

#side-bar:not(.init) {
	animation: .5s side-bar-expand forwards;
}

#side-bar.contracted {
	animation: .5s side-bar-contract forwards;
}

@keyframes side-bar-expand {
	to {
		width: 350px;
	}
}

@keyframes side-bar-contract {
	from {
		width: 350px;
	}
}
<div id='side-bar' class='init'>Click me</div>

Just change to to from in side-bar-contract

@keyframes side-bar-expand { to { width: 350px; } }
@keyframes side-bar-contract { from { width: 350px; } }

let el = document.querySelector('#side-bar');
el.addEventListener('click', evt => el.classList.toggle('contracted'));
#side-bar {
	height: 100%;
	width: 75px;
	background: #d4653c;
	position: fixed;
	left: 0;
	top: 0;
	padding: .8rem;
}

#side-bar:not(.contracted) {
	animation: .5s side-bar-expand forwards;
}

#side-bar.contracted {
	animation: .5s side-bar-contract forwards;
}

@keyframes side-bar-expand {
	to {
		width: 350px;
	}
}

@keyframes side-bar-contract {
	from {
		width: 350px;
	}
}
<div id='side-bar' class='contracted'>Click me</div>

Upvotes: 4

Pete
Pete

Reputation: 58462

Why not just use a transition animation:

let el = document.querySelector('#side-bar');
el.addEventListener('click', evt => el.classList.toggle('contracted'));
#side-bar {
  height: 100%;
  width: 350px;                         /* have width at 350px when not contracted */
  background: #d4653c;
  position: fixed;
  left: 0;
  top: 0;
  padding: .8rem;
  transition: width .5s;   /* animate the width */
}

#side-bar.contracted {
  width: 75px;
}
<div id='side-bar' class='contracted'>Click me</div>

If you need to use keyframes then you need to start the second one off at 350px - you start it at 75 to 75 which is why it doesn't animate:

let el = document.querySelector('#side-bar');
el.addEventListener('click', evt => el.classList.toggle('contracted'));
#side-bar {
  height: 100%;
  width: 75px;
  background: #d4653c;
  position: fixed;
  left: 0;
  top: 0;
  padding: .8rem;
}

#side-bar:not(.contracted) {
  animation: .5s side-bar-expand forwards;
}

#side-bar.contracted {
  animation: .5s side-bar-contract forwards;
}

@keyframes side-bar-expand {
  to {
    width: 350px;
  }
}

@keyframes side-bar-contract {
  0% {
    width: 350px;
  }
  100% {
    width: 75px;
  }
}
<div id='side-bar' class='contracted'>Click me</div>

Upvotes: 2

brianespinosa
brianespinosa

Reputation: 2408

First, I would recommend you do this with hover styles and css transition instead of an animation for something as simple as animating a single property.

.class {
  width: 400px;
  transition: width 1500ms ease-in-out;
}

.class:hover {
  width: 100px;
}

CSS transition will actually stop part way through the transition and reverse to the initial size for you.

Second, I would recommend that you do not animate or transition the width property in CSS. Here's a great article about what properties you should avoid animating.

If you need to delay a transition from happening on other elements, you can use the transition-delay property. This property can also be applied in hover effects... including with hover effects on parent elements. So you may potentially have multiple hover effects in play at a given time to accomplish your desired effect.

Upvotes: 1

Related Questions