How to play reverse keyframe animation for hover off?

…衆ロ難τιáo~ 提交于 2019-12-07 20:06:31

问题


I'm trying to build a dropdown menu where first the dropdown slides down, then one-by-one menu items appear from the bottom. On hover, it looks good but I don't know how to play it smoothly on hover off. What I'm trying to do is hide the second sub-menu item first, then the first submenu item, then the dropdown slides up.

What I tried is to add the hide animation keyframes with a delay by default to the dropdown, and add the show animation on hover with 0 delay. But its not working properly as you can see.(and it will animate on page load too, which is not good for me)

ul li ul {
  -webkit-animation: hideDropdown 1s;
  -webkit-animation-delay: 0.5s;
}

ul li:hover ul {
  -webkit-animation: showDropdown 1s forwards;
  -webkit-animation-delay: 0s;
}

Fiddle Demo (uses SCSS)


回答1:


As I had indicated in comments to the question, producing the reverse effect of an animation is always complex. It is not impossible but just that it requires extra coding (like extra keyframes) and tweaking of all related properties to achieve a perfect effect.

Using transition is the best option if there is a need to achieve the reverse effect also. It is perfectly possible to produce the effect that you are after using transitions and it is all very similar to how you'd do it with animations. In the :hover selector, apply the transition setting such that :first-child has a lower delay than the second (and subsequent children) so that it appears first and in the default selector apply the transition setting such that the :first-child has a higher delay than the rest.

Using transitions would also avoid the animation being shown on page load itself. This is because the transitions only happen when there is a state change from one to another.

body {
  background: #f1f1f1;
}

ul {
  height: 100px;
  position: relative;
  background: #fff;
}
ul li {
  display: block;
  float: left;
  margin: 0;
  list-style-type: none;
  padding: 0;
}
ul li a {
  display: block;
  height: 100px;
  line-height: 100px;
  padding: 0 20px;
}
ul li ul {
  position: absolute;
  left: 0px;
  right: 0px;
  top: 100px;
  height: 0px;
  opacity: 0;
  visibility: hidden;
  transition: all 1s 0.5s;
}
ul li ul li {
  position: absolute;
  left: 50px;
  bottom: 0px;
  width: 200px;
  overflow: hidden;
  height: 100px;
}
ul li ul li a {
  display: block;
  height: 100px;
  background: red;
  padding: 0 20px;
  position: absolute;
  opacity: 0;
  transform: translate(0px, 100px);
  transition: all 1s 0.2s;
}
ul li ul li:first-child a {
  transition-delay: 0.4s;
}
ul li ul li:last-child {
  left: 250px;
}
ul li ul li:last-child a {
  transition-delay: 0.2s;
}
ul li:hover ul {
  height: 100px;
  opacity: 1;
  visibility: visible;
  transition: all 1s 0s;
}
ul li:hover ul li a {
  opacity: 1;
  transform: translate(0px, 0px);
  transition: all 1s 0.2s;
}
ul li:hover ul li:last-child a {
  transition-delay: 0.4s;
}
<ul>
  <li>
    <a href="#">Dropdown</a>
    <ul>
      <li><a href="">First</a></li>
      <li><a href="">second</a></li>
    </ul>
  </li>
  <li><a href="">Not dropdown</a></li>
</ul>

Note: In the above snippet, I've used the compiled CSS and removed all the -webkit- prefixes to make it viewable on all browsers. If you want the SCSS code with the prefixes, you can find it here.


All that said, I still want to explain what was creating the problem with your animation demo. You had done a lot of work correct and you were almost close to achieving what you want.

The way animations work is such that once the selector that is applying the animation is not applicable any longer, the animation gets removed (lost) and the element immediately snaps back to its default or original state. Even though another animation was being applied on hover out, the properties specified within the reverse animation's keyframe settings do not get applied until their delay time is elapsed. This means that the ul and a snap back to being invisible (either through opacity or visibility) setting, become visible again when the animation starts (through from keyframe) and then animate to the end state (again invisible through the to keyframe).

The solution to this problem is to set animation-fill-mode: backwards to the ul and a (unhovered state). This setting would mean that the element takes the properties specified in the from keyframes even during the delay period.

body {
  background: #f1f1f1;
}
ul {
  height: 100px;
  position: relative;
  background: #fff;
}
ul li {
  display: block;
  float: left;
  margin: 0;
  list-style-type: none;
  padding: 0;
}
ul li a {
  display: block;
  height: 100px;
  line-height: 100px;
  padding: 0 20px;
}
ul li ul {
  position: absolute;
  left: 0px;
  right: 0px;
  top: 100px;
  height: 0px;
  visibility: hidden;
  animation: hideDropdown 1s;
  animation-delay: 0.5s;
  animation-fill-mode: backwards;
}
ul li ul li {
  position: absolute;
  left: 50px;
  bottom: 0px;
  width: 200px;
  overflow: hidden;
  height: 100px;
}
ul li ul li a {
  display: block;
  height: 100px;
  background: red;
  padding: 0 20px;
  position: absolute;
  opacity: 0;
  transform: translate(0px, 100px);
  animation: hideDropdownItem 1s 0.2s;
  animation-fill-mode: backwards;
}
ul li ul li:first-child a {
  animation-delay: 0.4s;
}
ul li ul li:last-child {
  left: 250px;
}
ul li ul li:last-child a {
  animation-delay: 0.2s;
}
ul li:hover ul {
  animation: showDropdown 1s forwards;
  animation-delay: 0s;
}
ul li:hover ul li a {
  animation: showDropdownItem 1s forwards 0.2s;
}
ul li:hover ul li:last-child a {
  animation-delay: 0.4s;
}
@keyframes showDropdown {
  from {
    opacity: 0;
    height: 0;
    visibility: hidden;
  }
  to {
    height: 100px;
    opacity: 1;
    visibility: visible;
  }
}
@keyframes hideDropdown {
  from {
    height: 100px;
    opacity: 1;
    visibility: visible;
  }
  to {
    opacity: 0;
    height: 0;
    visibility: hidden;
  }
}
@keyframes showDropdownItem {
  from {
    opacity: 0;
    transform: translate(0px, 100px);
  }
  to {
    opacity: 1;
    transform: translate(0px, 0px);
  }
}
@keyframes hideDropdownItem {
  from {
    opacity: 1;
    transform: translate(0px, 0px);
  }
  to {
    opacity: 0;
    transform: translate(0px, 100px);
  }
}
<ul>
  <li>
    <a href="#">Dropdown</a>
    <ul>
      <li><a href="">First</a>
      </li>
      <li><a href="">second</a>
      </li>
    </ul>
  </li>
  <li><a href="">Not dropdown</a>
  </li>
</ul>

Note: Same as with first snippet, the above one also uses plain CSS with no prefixes. SCSS version with prefixes is available here.

But as mentioned earlier, this wouldn't prevent the animation from being visible on page load. The only way to stop that is by using JavaScript.



来源:https://stackoverflow.com/questions/35196848/how-to-play-reverse-keyframe-animation-for-hover-off

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!