前端蓄力元气波动画

525 阅读3分钟

先看效果,先看效果

元气波.gif

话不多说,直接上代码。

html 部分
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>svg 练习</title>
    <style>
      body {
        background-color: rgba(0, 0, 0);
      }

      .pointer {
        position: absolute;
        width: 10px;
        height: 10px;
        background: rgba(255, 255, 255, 0.5);
        border-radius: 50%;
        transform: scale(0);
        opacity: 1;
        animation: ripple 0.3s linear forwards;
        pointer-events: none;
      }

      @keyframes ripple {
        to {
          transform: scale(6);
          opacity: 0;
        }
      }

      .ball {
        position: absolute;
        width: 80px;
        height: 80px;
        border-radius: 50%;
        transform-origin: 50% !important;
        background: #f9db01;
      }
    </style>
  </head>
  <body>
    <div class="ball"></div>
  </body>
</html>
<script src="./js/index.js"></script>

简单的过一遍 html 代码,div 类名 ball;顾名思义就是一个小球,咱们主要围绕这个小球做动画;而另一个类名 pointer,则是为了点击屏幕时,让我们更直观的看到鼠标在哪里的一个小圆圈扩散渐隐效果;ripple 则是扩散渐隐动画。

js部分
window.addEventListener("click", (e) => {
  const pointer = document.createElement("div");
  pointer.classList.add("pointer");
  pointer.style.left = `${e.clientX}px`;
  pointer.style.top = `${e.clientY}px`;
  document.body.appendChild(pointer);
  pointer.addEventListener("animationend", () => {
    pointer.remove();
  });
});

const ball = document.querySelector(".ball");

function init() {
  const x = window.innerWidth / 2;
  const y = window.innerHeight / 2;
  ball.style.transform = `translate(${x}px, ${y}px)`;
}

init();

window.addEventListener("click", (e) => {
  const x = e.clientX;
  const y = e.clientY;
  move(x, y);
});

function move(x, y) {
  // 获取小球的中心点坐标
  const rect = ball.getBoundingClientRect();
  const ballX = rect.left + rect.width / 2;
  const ballY = rect.top + rect.height / 2;

  // 多次点击时,先清除之前的动画
  ball.getAnimations().forEach((animation) => {
    animation.cancel(); // 取消之前的动画
  });
  
  // 小球在当前位置与鼠标点击位置的夹角,咱们用反正切来计算
  const rad = Math.atan2(y - ballY, x - ballX);
  const deg = Math.round((rad * 180) / Math.PI); // 将弧度转换为角度
  
  // 这里就是 web animation api 的使用;
  ball.animate(
    [
      { transform: `translate(${ballX}px, ${ballY}px) rotate(${deg}deg)` },
      {
        transform: `translate(${ballX}px, ${ballY}px) rotate(${deg}deg) scaleX(1.5)`,
        offset: 0.6,
      },
      {
        transform: `translate(${x}px, ${y}px) rotate(${deg}deg) scaleX(1.5)`,
        offset: 0.8,
      },
      { transform: `translate(${x}px, ${y}px) rotate(${deg}deg)` },
    ],
    {
      duration: 1000,
      fill: "forwards",
    }
  );
}

咱简单过一遍 js;

  • 第一个点击事件监听是创建一个div,给这个div添加类名pointer,也是上面说的则是为了让我们点击屏幕时,更直观的看到鼠标在哪里的一个小圆圈扩散渐隐效果
  • init 函数,则是为了让我们的小球初始化于咱们浏览器屏幕的中央;咱要一开始就初始化。
  • 第二个点击事件监听,则是让我们的小球跟随咱的鼠标点击方向进行move动画,当然,我们要把鼠标点击的x坐标和y坐标传给 move 函数
  • move 函数;接收传过来的鼠标点击的x坐标和y坐标;获取小球中心点的坐标,计算小球中心点位置与鼠标点击位置的横纵坐标形成的夹角角度;调用web animation api;这个api接收两个参数,第一个参数为关键帧数组,第二个参数为动画的配置,包括动画持续时间 duration 等等;咱们这里传入四个关键帧,前两个关键帧用于小球变形旋转,后两个关键帧用于小球移动且变回原来的样子,咱们的 fill: "forwards" 配置是为了让小球在最后停止在我们鼠标点击的位置,如果不配置这个的话,小球会回到一开始进行动画的位置,即屏幕中心点。
  • 效率问题,我们多次点击屏幕,会一直不断的加动画,所以咱们要处理一下, ball.getAnimations() 就是获取已经给ball添加的动画,该函数返回的是一个数组。咱们遍历它,且调用 cancel 方法,来对之前的所有动画进行取消动画操作

值得一提的是,动画部分,使用了一个新的API,即 WEB Animation API;该动画比普通的js动画更为高效,因为它并不会改变DOM树,不会阻塞浏览器渲染管线的主线程,相当于css动画一样,但是又比css动画更好配置。

当然这个动画还有不足的地方,比如tranform变换的原点不在小球中心,JYM可以自己弄一下