如何播放反向关键帧动画以悬停?

2022-01-22 00:00:00 hover css css-animations css-transitions

我正在尝试构建一个下拉菜单,首先下拉菜单会向下滑动,然后从底部逐一显示菜单项.悬停时,它看起来不错,但我不知道如何在悬停时顺利播放.我要做的是先隐藏第二个子菜单项,然后是第一个子菜单项,然后下拉菜单向上滑动.

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.

我尝试的是在默认情况下将隐藏动画关键帧添加到下拉列表中,并在悬停时添加显示动画,延迟为 0.但是你可以看到它不能正常工作.(它也会在页面加载时动画,这对我不利)

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;
}

小提琴演示(使用 SCSS)

推荐答案

正如我在问题的评论中指出的那样,产生动画的反向效果总是很复杂.这并非不可能,只是需要额外的编码(如额外的关键帧)和调整所有相关属性才能达到完美的效果.

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.

如果还需要实现反向效果,则使用 transition 是最佳选择.完全有可能在使用过渡之后产生效果,这与使用动画的方式非常相似.在 :hover 选择器中,应用 transition 设置,使得 :first-child 的延迟低于第二个(以及后续的子级),因此它首先出现并在默认选择器中应用 transition 设置,这样 :first-child 的延迟比其他的要高.

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>

注意:在上面的代码片段中,我使用了编译后的 CSS 并删除了所有 -webkit- 前缀,使其在所有浏览器上都可以查看.如果你想要带有前缀的 SCSS 代码,你可以在这里找到它.

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.

说了这么多,我仍然想解释是什么造成了您的 animation 演示的问题.你做了很多正确的工作,你几乎接近实现你想要的.

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.

动画的工作方式是,一旦应用动画的选择器不再适用,动画就会被移除(丢失),并且元素会立即恢复到其默认或原始状态.即使在悬停时应用了另一个动画,在反向动画的关键帧设置中指定的属性也不会被应用直到它们的延迟时间过去.这意味着 ula 恢复为不可见(通过 opacityvisibility 设置),变为动画开始时再次可见(通过 from 关键帧),然后动画到结束状态(通过 to 关键帧再次不可见).

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).

解决这个问题的方法是将animation-fill-mode:backwards设置为ula(未悬停状态).此设置意味着该元素即使在延迟期间也会采用 from 关键帧中指定的属性.

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>

注意: 与第一个片段相同,上面的片段也使用没有前缀的纯 CSS.此处提供带前缀的 SCSS 版本.

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

但如前所述,这不会阻止动画在页面加载时可见.阻止这种情况的唯一方法是使用 JavaScript.

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.

相关文章