先看效果,先看效果
话不多说,直接上代码。
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可以自己弄一下