原生JS实现进场与退场动画:从CSS类切换至animationend监听

作者:小圆点

时间:2026-10-25

浏览:154

本文详解如何使用原生JavaScript实现元素的进场与退场动画。不同于仅添加CSS类,核心在于监听animationend事件以判断动画结束时机,从而安全移除元素或执行后续逻辑。通过对比Vue的transition机制,展示了如何在非框架环境下自由控制动画流程,适用于需要精确控制DOM生命周期的高级前端场景。

文章目录

前言

作为前端开发者,编写并添加基础动画效果是必备技能。然而,实践中许多人仅掌握进场动画,对退场动画的实现知之甚少,且往往采用简单粗暴的CSS类添加方式,缺乏对动画生命周期的深入理解。

以常见的下拉选项展开动画为例,我们需要预先定义好keyframe关键帧以及animation动画属性的具体数值,以便控制动画的行为和样式变化。

.expand {
    transform-origin: top;
    animation: expand 0.3s ease both;
}

.fold {
    transform-origin: top;
    animation: fold 0.3s ease both;
}

/* 展开 */
@keyframes expand {
    from {
        transform: scaleY(0);
    }
    to {
        transform: scaleY(1);
    }
}

/* 折叠 */
@keyframes fold {
    from {
        transform: scaleY(1);
    }
    to {
        transform: scaleY(0);
    }
}

在实际应用中,只需将该CSS类直接绑定到目标元素上,即可呈现出预期的视觉效果,如常见的展开或折叠动作。

前言

尽管这种简单方式能满足大部分业务需求,且多数用户不会察觉退场是否有动画,但对于追求技术深度的开发者而言,探究退场动画的实现原理至关重要。由于早期直接切入Vue等框架的学习,这一原生实现细节常被忽视。

Vue中的transition标签

引入Vue框架后,处理退场动画变得异常简便。通过配置如下的组件,可以自动管理进场和退场动画的状态切换,无需手动干预DOM的显示与隐藏逻辑。

<transition enter-active-class="expand" leave-active-class="fold" appear>
	<ul class="options-ul" v-show="isShow">
        <li class="option">雾切之回光</li>
        <li class="option">飞雷之弦振</li>
        <li class="option">薙草之稻光</li>
        <li class="option">波乱月白经津</li>
    </ul>
</transition>

随着对Vue框架熟练度的提升,通过开发者工具(DevTools)观察发现:入场动画开始时,目标元素会获得expand类;动画结束后,该expand类被移除。而在退场动画执行期间,元素会被添加fold类,待退场动画完成后,fold类移除,元素随之消失。

为了深入理解其内部机制,我研究了Vue的transition组件源码。详细原理可参考相关技术文章,主要揭示了框架如何自动处理类名的添加与移除。

核心原理在于利用一个相对冷门的监听事件animationend来精确捕捉动画执行的结束时刻,进而触发后续的DOM操作或状态更新。

小DEMO

基于上述原理,我们可以编写一个纯原生的小示例。核心代码逻辑如下所示,展示了如何手动控制类名切换及事件监听。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>全原生样式的下拉框</title>
</head>
<style>
  html,
  body {
    margin: 0;
  }

  html{
    --arrowSize: 8px;
  }

  div, ul {
    box-sizing: border-box;
  }

  html, body {
    width: 100%;
    height: 100%;
    background-color: #0B2550;
    overflow: hidden;
  }

  #app {
    width: 1000px;
    height: 500px;
    box-shadow: 5px 5px 5px rgba(0, 0, 0, 0.1);
    box-sizing: border-box;
    border: 1px solid white;
    position: relative;
    margin: 100px auto;
    background-color: #0B2550;
  }

  .selection-wrapper{
    position: relative;
    width: 250px;
    height: 50px;
    color: white;
    margin: 20px auto;
    font-size: 30px;
    font-family: 华文楷体;
    /*border: 1px solid #799cdc;*/
    border-radius: 10px;
  }

  .displayText-box{
    width: 100%;
    height: 100%;
    text-align: center;
    line-height: 50px;
    cursor: pointer;
  }

  .displayText{
    user-select: none;
  }

  .arrow-icon{
    position: absolute;
    top: 22px;
    right: 10px;
    width: 16px;
    height: 8px;
    line-height: initial;
    font-size: 0;
    transition: transform 0.3s ease;
  }

  .options-ul{
    display: none;
    width: 100%;
    padding: 0;
    margin: 0;
    position: absolute;
    top: 60px;
    left: 0;
    border: 2px solid #BDF0FF;
  }

  .option{
    list-style: none;
    text-align: center;
    line-height: 40px;
    cursor: default;
  }

  .expand {
    transform-origin: top;
    animation: expand 0.3s ease both;
  }

  .fold {
    transform-origin: top;
    animation: fold 0.3s ease both;
  }

  /* 展开 */
  @keyframes expand {
    from {
      transform: scaleY(0);
    }
    to {
      transform: scaleY(1);
    }
  }

  /* 折叠 */
  @keyframes fold {
    from {
      transform: scaleY(1);
    }
    to {
      transform: scaleY(0);
    }
  }
</style>
<body>
  <div id="app">
    <h1 style="text-align: center;color: white;font-family: 华文楷体">选择你想要的五星武器</h1>
    <div class="selection-wrapper">
      <div class="displayText-box">
        <div class="displayText">请选择</div>
        <div class="arrow-icon">
          <svg viewBox="0,0,16,8">
            <path d="M 0,0 L 16,0 L 8,8 Z" fill="#31A1EF"></path>
          </svg>
        </div>
      </div>
      <ul class="options-ul expand">
        <li class="option">雾切之回光</li>
        <li class="option">飞雷之弦振</li>
        <li class="option">薙草之稻光</li>
        <li class="option">波乱月白经津</li>
      </ul>
    </div>
  </div>
</body>
<script>
  const selection = document.querySelector('.displayText-box');
  const ul = document.querySelector('.options-ul');
  const arrow = document.querySelector('.arrow-icon');
  selection.addEventListener('click',function (e){
    let isExpand = ul.style.display === 'block';
    if(isExpand){
      // 折叠
      ul.classList.add('fold');
      arrow.style.transform = 'rotate(0deg)'
      ul.onanimationend = function (){
        ul.classList.remove('fold');
        ul.style.display = 'none';
      }
    } else {
      // 展开
      ul.style.display = 'block';
      ul.classList.add('expand');
      arrow.style.transform = 'rotate(180deg)'
      ul.onanimationend = function (){
        ul.classList.remove('expand');
      }
    }
  })
</script>
</html>

运行该示例的效果如下所示,可以直观看到下拉列表展开与折叠的动画过程。

前言

结语

这个DEMO就是一个最简单的退场动画的实现方式,即在animationend事件中使目标元素消失。当我们清楚原生的js是怎么写的,那么在非vue环境中,我们也可以自由自在的添加各种各样的退场动画了。不过,有能力的小伙伴可能就直接去看vue的transition的源码去了,而我这篇文章就是献给像我一样不想去看源码但是还想知道一些旁门左道的知识的小伙伴。

到此这篇关于使用原生JS添加进场和退场动画详解的文章就介绍到这了,更多相关JS进场 退场动画内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章