Jon Snow
23 April 2023
There are many Javascript animation libraries out there, but Anime.js is one of the best. it's easy to use, has a small and simple API, and offers everything you could want from a modern animation engine. The library has a small file size and supports all modern browsers, including IE/Edge 11+.
Inspired by Valery Alikin
https://codepen.io/AlikinVV/pen/OrmJxj
npm install animejs
import anime from "animejs";
const AnimeJsExample = () => {
return (
<>
<div className="my-5" style={{
display: "flex",
justifyContent: "center",
}}>
<div className="wrap">
<div className="stepper">
<span className="count first active hide">15</span>
<span className="count second next" />
</div>
<img
src="https://alikinvv.github.io/stepper-iteration/build/img/arrow-top.svg"
alt="waiting"
className="arrow-top"
/>
<img
src="https://alikinvv.github.io/stepper-iteration/build/img/arrow-bottom.svg"
alt="waiting"
className="arrow-bottom"
/>
</div>
{/* <span className="desc">Hold & Drag</span> */}
</div>
</>
);
};
import "./AnimeJsExample.css";
.animation-wrapper {
position: relative;
width: 100%;
padding-bottom: 40%;
}
/** Layered Animation **/
.layered-animations {
position: absolute;
top: 50%;
left: 50%;
display: flex;
align-items: center;
justify-content: center;
width: 1100px;
height: 550px;
margin: -275px 0 0 -550px;
}
.layered-animations .shape {
position: absolute;
top: 50%;
overflow: visible;
width: 280px;
height: 280px;
margin-top: -140px;
stroke: transparent;
stroke-width: 1px;
fill: url(#shapesGradient);
}
@media (min-width: 740px) {
.layered-animations .shape {
stroke-width: .5px;
}
}
.layered-animations .small.shape {
width: 64px;
height: 64px;
margin-top: -32px;
stroke: currentColor;
fill: purple;
}
.layered-animations .x-small.shape {
width: 32px;
height: 32px;
margin-top: -16px;
stroke: currentColor;
fill: currentColor;
}
import React, { useEffect } from "react";
useEffect(() => {
let mousePos = 0;
let currentPos = 0;
let position = 0;
let draggable = false;
let blockAnime;
let countAnimePlus = anime.timeline();
let countAnimeMinus = anime.timeline();
let offset = 130;
let direction;
let dur = 100;
let count = parseInt(document.querySelector(".active").textContent);
document.addEventListener("mousedown", function (event) {
currentPos = mousePos;
draggable = true;
blockAnime.pause();
let first = document.querySelector(".first");
let second = document.querySelector(".second");
if (first.classList.contains("active")) {
first.classList.remove("active");
first.classList.add("next");
second.classList.remove("next");
second.classList.add("active");
} else if (second.classList.contains("active")) {
second.classList.remove("active");
second.classList.add("next");
first.classList.remove("next");
first.classList.add("active");
}
if (direction === "plus") {
countAnimePlus.pause();
}
if (direction === "minus") {
countAnimeMinus.pause();
}
});
document.addEventListener("mousemove", function (event) {
mousePos = event.pageY;
if (draggable) {
position = mousePos - currentPos;
document.querySelector(".stepper").style.transform =
"translateY(" + position / 2 + "px)";
}
if (position <= offset * -1 && draggable) {
center();
count++;
plus();
}
if (position >= offset && draggable) {
center();
count--;
minus();
}
});
document.addEventListener("mouseup", function (event) {
if (draggable) {
center();
}
});
function center() {
draggable = false;
blockAnime = anime({
targets: ".stepper",
duration: dur,
translateY: 0,
});
}
function plus() {
direction = "plus";
countAnimePlus = anime.timeline();
let next = document.querySelector(".next");
let active = document.querySelector(".active");
next.textContent = count;
next.style.transform = "translateY(-100px) translateX(-50%)";
countAnimePlus
.add({
targets: active,
translateY: 100,
translateX: "-50%",
duration: dur,
})
.add({
targets: next,
translateY: 0,
translateX: "-50%",
duration: 1500,
offset: "-=" + dur,
});
}
function minus() {
direction = "minus";
countAnimeMinus = anime.timeline();
let next = document.querySelector(".next");
let active = document.querySelector(".active");
next.textContent = count;
next.style.transform = "translateY(100px) translateX(-50%)";
countAnimeMinus
.add({
targets: active,
translateY: -100,
translateX: "-50%",
duration: dur,
})
.add({
targets: next,
translateY: 0,
translateX: "-50%",
duration: 1500,
offset: "-=" + dur,
});
}
center();
plus();
setTimeout(() => {
document.querySelector(".hide").classList?.remove("hide");
}, 300);
}, []);
import React, { useEffect } from "react";
import anime from "animejs";
import "./AnimeJsExample.css";
const AnimeJsExample = () => {
useEffect(() => {
let mousePos = 0;
let currentPos = 0;
let position = 0;
let draggable = false;
let blockAnime;
let countAnimePlus = anime.timeline();
let countAnimeMinus = anime.timeline();
let offset = 130;
let direction;
let dur = 100;
let count = parseInt(document.querySelector(".active").textContent);
document.addEventListener("mousedown", function (event) {
currentPos = mousePos;
draggable = true;
blockAnime.pause();
let first = document.querySelector(".first");
let second = document.querySelector(".second");
if (first.classList.contains("active")) {
first.classList.remove("active");
first.classList.add("next");
second.classList.remove("next");
second.classList.add("active");
} else if (second.classList.contains("active")) {
second.classList.remove("active");
second.classList.add("next");
first.classList.remove("next");
first.classList.add("active");
}
if (direction === "plus") {
countAnimePlus.pause();
}
if (direction === "minus") {
countAnimeMinus.pause();
}
});
document.addEventListener("mousemove", function (event) {
mousePos = event.pageY;
if (draggable) {
position = mousePos - currentPos;
document.querySelector(".stepper").style.transform =
"translateY(" + position / 2 + "px)";
}
if (position <= offset * -1 && draggable) {
center();
count++;
plus();
}
if (position >= offset && draggable) {
center();
count--;
minus();
}
});
document.addEventListener("mouseup", function (event) {
if (draggable) {
center();
}
});
function center() {
draggable = false;
blockAnime = anime({
targets: ".stepper",
duration: dur,
translateY: 0,
});
}
function plus() {
direction = "plus";
countAnimePlus = anime.timeline();
let next = document.querySelector(".next");
let active = document.querySelector(".active");
next.textContent = count;
next.style.transform = "translateY(-100px) translateX(-50%)";
countAnimePlus
.add({
targets: active,
translateY: 100,
translateX: "-50%",
duration: dur,
})
.add({
targets: next,
translateY: 0,
translateX: "-50%",
duration: 1500,
offset: "-=" + dur,
});
}
function minus() {
direction = "minus";
countAnimeMinus = anime.timeline();
let next = document.querySelector(".next");
let active = document.querySelector(".active");
next.textContent = count;
next.style.transform = "translateY(100px) translateX(-50%)";
countAnimeMinus
.add({
targets: active,
translateY: -100,
translateX: "-50%",
duration: dur,
})
.add({
targets: next,
translateY: 0,
translateX: "-50%",
duration: 1500,
offset: "-=" + dur,
});
}
center();
plus();
setTimeout(() => {
document.querySelector(".hide").classList?.remove("hide");
}, 300);
}, []);
return (
<>
<div className="my-5" style={{
display: "flex",
justifyContent: "center",
}}>
<div className="wrap">
<div className="stepper">
<span className="count first active hide">15</span>
<span className="count second next" />
</div>
<img
src="https://alikinvv.github.io/stepper-iteration/build/img/arrow-top.svg"
alt="waiting"
className="arrow-top"
/>
<img
src="https://alikinvv.github.io/stepper-iteration/build/img/arrow-bottom.svg"
alt="waiting"
className="arrow-bottom"
/>
</div>
{/* <span className="desc">Hold & Drag</span> */}
</div>
</>
);
};
export default AnimeJsExample;