纯 CSS 实现子菜单逐渐弹出动画

1,033 阅读4分钟

CSS 实现子菜单逐渐弹出动画

本人属实 CSS 菜鸟,CSS 写的非常辣鸡,还请各位大佬轻喷

最近在使用 utools 中的一个插件时发现一个子菜单的弹出动画比较有意思

Honeycam 2022-06-11 17-23-00.gif

具体的说就是 hover 菜单图标时,子菜单从左到右依次显示,并且有缩放的效果。于是想着能不能不通过 js 方式而使用纯 css 来实现。最后通过不懈努力,终于实现了类似的效果,你可以通过这个 demo 体验codesandbox.io/s/html-css-…

hover.gif

虽然不是特别完美,但是在这个过程中也算是了解了 css 动画的一些基础。

transform 以及 transition

transform 以及 transition 是 css 中实现动画的一种手段。一般来说,transform 定义了动画的最终状态,而 transition 定义了状态改变的参数。例如:

p: hover{
    transform: scale(1.5);
    transition-delay: 150ms;
    transition-duration: 100ms;
}

表示当 p 标签被 hover 时会放大 1.5 倍。而 transition-delay 以及 transition-duration 则分别表示这个状态改变延迟 150ms 执行,以及这个状态的改变会持续 100ms。当然 transform 以及 transition 对应的状态以及参数还有很多,这里就不一一列出。

回过头来看我们的需求:

  • 子菜单逐渐显示:可以通过延时实现。例如每个子菜单的延时依次增大,则表现出一一显示的效果。
  • 子菜单显示过程中伴随缩放: 即对应着 transform: scale 状态的改变

具体实现

首先完成基本框架:

    <div id="wrap">
        <div id="trigger"></div>
        <div class="items">
            <div class="item"></div>
            <div class="item"></div>
            <div class="item"></div>
        </div>
    </div>

其中 wrap 时整个 demo 的容器,没有什么具体的作用。trigger 则表示我们用于触发子菜单显示的图标。而 items 则是正真的子菜单。每个 item 代表一个子菜单项目。

增加一些初始样式:

 #wrap {
        display: flex; /*flex 布局,使得 trigger 以及 items 显示在一行*/
        justify-content: left;
        margin-top: 100px;
        margin-left: 100px;
        }
#trigger{
        width: 50px;
        height: 50px;
        border-radius: 50%;
        background-color: rgb(44, 56,126);
        box-shadow: 0 0 10px 1px rgba(0, 0, 0, 0.3);
        box-shadow:  rgba(0, 0, 0, 0.5);
}
.items {
            display: flex; /*flex 布局,使得所有的 item 显示在一行*/
            visibility: hidden; /* 子菜单初始应该 hidden */
        }
        .item {
            width: 50px;
            height: 50px;
            border-radius: 50%;
            transform: scale(0); /* 子菜单初始状态为 scale(0) 这样到 scale(1) 时就会出现缩放的效果 */
            visibility: hidden;
            box-shadow: 0 0 10px 1px rgba(0, 0, 0, 0.3);
            margin-left: 20px;
        }

随后为了 hover 子菜单依次出现,并出现缩放的效果,我们需要定义 hover 的样式:

#trigger:hover + .items .item:nth-of-type(1){
    transform: scale(1);
    visibility: visible;
    transition-delay: 200ms; /* 显示的延迟依次增加,呈现一一显示的效果 */
    transition-duration: 150ms;
}
#trigger:hover + .items .item:nth-of-type(2){
    visibility: visible;
    transition-delay: 300ms; /* 显示的延迟依次增加,呈现一一显示的效果 */
    transform: scale(1);
    transition-duration: 150ms;
}
#trigger:hover + .items .item:nth-of-type(3){
    visibility: visible;
    transition-delay: 400ms; /* 显示的延迟依次增加,呈现一一显示的效果 */
    transform: scale(1);
    transition-duration: 150ms;
}

这里主要使用 nth-of-type(i) 来选择不同的子菜单,并左到右依次增加 transition-delay 的时间,使得出现依次显示的效果。同样,但 trigger 失去 hover 时,我们需要一个从右到左依次增加的延迟时间。这样使得出现时为从左到右,消失时为从右到左:

        .item:nth-of-type(1){
            transition-delay: 300ms;
            transition-duration: 150ms;
        }
        
        .item:nth-of-type(2){
            transition-delay: 200ms;
            transition-duration: 150ms;
        }

        .item:nth-of-type(3){
            transition-delay: 150ms;
            transition-duration: 150ms;
        }

此时动画以及基本完成了,但是还有一个 bug。即一旦鼠标离开 trigger 去点击子菜单,trigger 就会失去 hover 的效果,子菜单就会消失。一个不能点击的子菜单明显不是我们需要的。于是我们还需要增加 items 的 hover 效果。这样就算鼠标离开 trigger,但只要在 items 之上子菜单也不会消失:


        .items:hover .item{
            transform: scale(1);
            visibility: visible;
        }

总结

实际上原理也非常简单:

  • transform: scale() 实现缩放效果
  • 结合 nth-of-type(i) 以及 transition-delay 实现依次出现的效果

最终的结果也有一些瑕疵,例如动画效果明显不够丝滑,多次短时间的 hover 容易卡顿等问题。如果各位大佬知道有更好的实现方式,请通过评论区带带我。