简介
Anime.js 是一个轻量级的 JavaScript 动画库,它提供了简单而强大的 API 来创建各种复杂的动画效果。以下是 Anime.js 的主要使用方法和特性:
安装
npm install animejs
基本用法
<script setup>
import { ref, onMounted } from "vue";
import anime from "animejs";
const rotations = ref(0);
const logoRef = ref(null);
const handleClick = () => {
rotations.value += 1;
anime({
targets: logoRef.value,
rotate: rotations.value * 360,
easing: "easeOutQuart",
duration: 2000,
});
};
onMounted(() => {
anime({
targets: logoRef.value,
scale: [
{ value: 1.25, easing: "easeInOutCubic", duration: 200 },
{ value: 1, easing: "spring(1, 80, 10, 0)" },
],
loop: true,
direction: "alternate",
loopDelay: 250,
});
});
</script>
<template>
<div class="mt-10">
<img ref="logoRef" src="@/assets/react.svg" class="logo" alt="React logo" />
<button
class="px-4 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 transition-colors mt-4"
@click="handleClick"
>
旋转
</button>
</div>
</template>
定时器(timer)
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
const time = ref(0);
const count = ref(0);
let interval = null;
onMounted(() => {
let currentIteration = 0;
interval = setInterval(() => {
time.value = Date.now();
currentIteration += 1;
count.value = currentIteration;
}, 1000 / 30);
});
onUnmounted(() => {
clearInterval(interval);
});
</script>
<template>
<div class="m-10 container mx-auto max-w-2xl">
<div class="flex flex-row justify-center items-center gap-8">
<div
class="relative w-64 h-24 rounded-lg overflow-hidden shadow-lg bg-[#6d402a] flex flex-col items-center justify-center"
>
<span class="text-lg text-white mb-2">current time</span>
<span
class="text-6xl font-mono font-bold tracking-widest text-[#ffa94d] lcd"
>{{ time }}</span
>
</div>
<div
class="relative w-64 h-24 rounded-lg overflow-hidden shadow-lg bg-[#6d402a] flex flex-col items-center justify-center"
>
<span class="text-lg text-white mb-2">callback fired</span>
<span
class="text-6xl font-mono font-bold tracking-widest text-[#ffa94d] lcd"
>{{ count }}</span
>
</div>
</div>
</div>
</template>
时间线(timeline)
<script setup>
import { onMounted } from "vue";
import anime from "animejs";
onMounted(() => {
const tl = anime.timeline({ duration: 750 });
tl.add({ targets: ".square", translateX: "15rem" })
.add({ targets: ".circle", translateX: "15rem" })
.add({ targets: ".triangle", translateX: "15rem", rotate: "1turn" });
});
</script>
<template>
<div class="flex gap-4 mt-10">
<div class="square w-16 h-16 bg-red-400 rounded"></div>
<div class="circle w-16 h-16 bg-green-400 rounded-full"></div>
<div
class="triangle"
style="width: 0; height: 0; border-left: 32px solid transparent; border-right: 32px solid transparent; border-bottom: 64px solid #60a5fa;"
></div>
</div>
</template>
动画(animate)
<script setup>
import { ref } from "vue";
import anime from "animejs";
const boxRef = ref(null);
const handleAnimate = () => {
anime({
targets: boxRef.value,
opacity: [0, 1],
scale: [0.5, 1.2, 1],
rotate: [0, 360],
duration: 1200,
easing: "easeInOutCubic",
});
};
</script>
<template>
<div class="mt-10 flex flex-col items-center">
<div ref="boxRef" class="w-24 h-24 bg-purple-400 rounded mb-4"></div>
<button
@click="handleAnimate"
class="px-4 py-2 bg-purple-600 text-white rounded"
>
动画
</button>
</div>
</template>
拖动(drag)
<script setup>
import { ref, onMounted } from "vue";
import interact from "interactjs";
const containerRef = ref(null);
const squareRef = ref(null);
onMounted(() => {
interact(squareRef.value).draggable({
inertia: true,
modifiers: [
interact.modifiers.restrictRect({
restriction: containerRef.value,
endOnly: true,
}),
],
listeners: {
move(event) {
const target = event.target;
const x = (parseFloat(target.getAttribute("data-x")) || 0) + event.dx;
const y = (parseFloat(target.getAttribute("data-y")) || 0) + event.dy;
target.style.transform = `translate(${x}px, ${y}px)`;
target.setAttribute("data-x", x);
target.setAttribute("data-y", y);
},
},
});
});
</script>
<template>
<div
ref="containerRef"
class="mt-10 w-[400px] h-[200px] bg-gray-100 relative flex items-center justify-center"
>
<div
ref="squareRef"
class="square w-16 h-16 bg-yellow-400 rounded absolute top-0 left-0 cursor-move"
></div>
</div>
</template>
滚动(scroll)
<script setup>
import { ref, onMounted, onUnmounted } from "vue";
import anime from "animejs";
const lastProgress = ref(0);
let ticking = false;
const onScroll = () => {
if (!ticking) {
window.requestAnimationFrame(() => {
const scrollY = window.scrollY;
const docHeight = document.body.scrollHeight - window.innerHeight;
const progress = Math.min(scrollY / docHeight, 1);
if (Math.abs(progress - lastProgress.value) > 0.001) {
lastProgress.value = progress;
// 1. 粉色盒子:分段动画
if (progress < 0.33) {
anime({
targets: ".scroll-box",
translateY: progress * 600,
scale: 1 + progress * 1.5,
opacity: 1,
rotate: 0,
background: "#f472b6",
duration: 400,
easing: "easeOutCubic",
});
} else if (progress < 0.66) {
anime({
targets: ".scroll-box",
translateY: 200 + (progress - 0.33) * 600,
scale: 1.5,
opacity: 1 - (progress - 0.33) * 1.5,
rotate: (progress - 0.33) * 720,
background: "#fbbf24",
duration: 400,
easing: "easeInOutCubic",
});
} else {
anime({
targets: ".scroll-box",
translateY: 400 + (progress - 0.66) * 600,
scale: 1.5 - (progress - 0.66) * 1.5,
opacity: 0.5 - (progress - 0.66) * 1.5,
rotate: 360,
background: "#34d399",
duration: 400,
easing: "easeInCubic",
});
}
// 2. 蓝色盒子:左右来回移动+弹性
anime({
targets: ".scroll-box2",
translateX: Math.sin(progress * Math.PI * 2) * 300,
scale: 1 + Math.abs(Math.cos(progress * Math.PI)),
rotate: progress * 360,
background: "#60a5fa",
duration: 500,
easing: "easeOutElastic(1, .5)",
});
// 3. 进度条
anime({
targets: ".scroll-progress",
scaleX: progress,
duration: 200,
easing: "linear",
});
// 4. 文字渐显
anime({
targets: ".scroll-text",
opacity: progress,
translateY: 100 - progress * 100,
duration: 400,
easing: "easeOutCubic",
});
}
ticking = false;
});
ticking = true;
}
};
onMounted(() => {
window.addEventListener("scroll", onScroll);
});
onUnmounted(() => {
window.removeEventListener("scroll", onScroll);
});
</script>
<template>
<div class="h-[2500px] relative">
<!-- 滚动进度条 -->
<div class="fixed top-0 left-0 w-full h-2 bg-gray-200 z-50">
<div
class="scroll-progress origin-left h-full bg-pink-400 scale-x-0"
></div>
</div>
<!-- 动画盒子1 -->
<div
class="scroll-box w-32 h-32 bg-pink-400 fixed top-20 left-20 rounded-lg shadow-lg"
></div>
<!-- 动画盒子2 -->
<div
class="scroll-box2 w-32 h-32 bg-blue-400 fixed top-60 left-60 rounded-lg shadow-lg"
></div>
<!-- 渐显文字 -->
<div
class="scroll-text fixed top-[300px] left-1/2 -translate-x-1/2 w-[600px] text-3xl text-gray-700 opacity-0"
>
<p>分段动画、弹性、渐变、旋转、缩放、透明度,全部联动!</p>
<p class="mt-10 text-xl">继续滚动,体验更丰富的滚动动画效果。</p>
</div>
<!-- 内容填充 -->
<div
class="absolute top-[700px] left-1/2 -translate-x-1/2 w-[600px] text-xl text-gray-700"
>
<p>你可以继续添加更多动画元素,或根据滚动区间分段控制动画效果。</p>
</div>
</div>
</template>
SVG 动画效果
<script setup>
import { ref, onMounted } from "vue";
import anime from "animejs";
const pathRef = ref(null);
onMounted(() => {
if (pathRef.value) {
const length = pathRef.value.getTotalLength();
pathRef.value.style.strokeDasharray = length;
pathRef.value.style.strokeDashoffset = length;
anime({
targets: pathRef.value,
strokeDashoffset: [length, 0],
duration: 2000,
easing: "easeOutCubic",
});
}
});
</script>
<template>
<div class="flex flex-col items-center mt-10">
<svg width="320" height="120" viewBox="0 0 320 120">
<path
ref="pathRef"
d="M20,60 Q160,10 300,60 T300,110"
stroke="#f472b6"
stroke-width="6"
fill="none"
/>
</svg>
<p class="mt-4 text-lg text-gray-700">SVG 路径描边动画</p>
</div>
</template>