今日はanime.jsのライブラリを紹介します。このライブラリを使う事で複雑なアニメーションをJavaScriptを使ってよりシンプルに書くことができます。
anime.jsはオープンソースなのでソースコードをGitHubからも見ることができます。CodePenから実際のデモを見ることもできるので見てみてください。

今日の完成プロジェクトはこのようになります。

今日の環境
- npmがインストールされている
- JavaScriptの基礎が理解できている
- CSSの基礎が理解できている
では、大体どのようなことができるかを理解できたところで早速、基本のコードを書いていきましょう。
プロジェクトの作成
今回はVanilla JavaScriptでプロジェクトを作成しますがビルドツールとしてViteを使います。
npm create vite@latest ✔ Project name: … animejs ✔ Select a framework: › Vanilla ✔ Select a variant: › JavaScript Scaffolding project in /home/dan/Documents/public/animejs/animejs... Done. Now run: cd animejs npm install npm run dev
anime.jsのインストール
では、npmを使ってanime.jsをインストールしましょう。
npm install animejs --save
コードを書こう
ではテキストエディタを開いてコードを書いていきます。
Bootstrapを使ってUIを作成したい場合はViteを使ったBootstrapの設定の仕方はこちらの記事を見てください。
では、index.htmlをこのように書いてみます。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Vite App</title> </head> <body> <button class="btn">Click Me</button> <script type="module" src="/main.js"></script> </body> </html>
次にmain.jsの中身を消して下記のように書いてみましょう。
import anime from 'animejs/lib/anime.es.js'; anime({ targets:'.btn', translateX: 250 })
そうすると、ブラウザがロードされたときに.btnクラスのHTML要素が右に動きましたね。
これが基本になります。

もし、クリックしたらボタンがアニメーションしたいときはこのようになります。
JavaScriptの基本に戻りましょう。
完成import anime from "animejs/lib/anime.es.js"; const btn = document.getElementsByClassName("btn"); btn[0].addEventListener("click", moveBtn); function moveBtn() { anime({ targets: ".btn", translateX: 250, }); }
完成するとこのようになりますね。
画像スライダーを作る
こちらを参考にしました。
index.htmlはこのようになります。
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>anime.js</title> </head> <body> <div class="slider"> <div class="nav"> <div class="next"></div> <div class="prev"></div> <div class="explore-btn">Explore</div> </div> <div class="item is-active"> <div class="content"> <div class="wrap">Hawaii</div> </div> <div class="imgs"> <div class="grid"> <div class="img img-1"><img src="./img/hawaii_1.jpg" /></div> <div class="img img-2"><img src="./img/hawaii_2.jpg" /></div> <div class="img img-3"><img src="./img/hawaii_3.jpg" /></div> <div class="img img-4"><img src="./img/hawaii_4.jpg" /></div> </div> </div> </div> <div class="item"> <div class="content"> <div class="wrap">New York</div> </div> <div class="imgs"> <div class="grid"> <div class="img img-1"><img src="./img/new_york_1.jpg" /></div> <div class="img img-2"><img src="./img/new_york_2.jpg" /></div> <div class="img img-3"><img src="./img/new_york_3.jpg" /></div> <div class="img img-4"><img src="./img/new_york_4.jpg" /></div> </div> </div> </div> <div class="item"> <div class="content"> <div class="wrap">Adferunt</div> </div> <div class="imgs"> <div class="grid"> <div class="img img-1"><img src="1.jpg" /></div> <div class="img img-2"><img src="2.jpg" /></div> <div class="img img-3"><img src="3.jpg" /></div> <div class="img img-4"><img src="4.jpg" /></div> </div> </div> </div> </div> <script type="module" src="./js/main.js"></script> </body> </html>
各gridクラスのdivタグ内に画像ファイルがあることに注目しましょう。
次に、styles.css(styles.scss)を書きます。
.slider { height: 100vh; width: 100vw; background-color: #0a0908; display: flex; align-items: center; justify-content: center; color: white; position: relative; overflow: hidden; transition: background-color 2s; } .slider .item .imgs { position: relative; width: 60%; padding-top: 60%; } .slider .item .imgs .grid { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: grid; grid-template-columns: repeat(12, 1fr); grid-template-rows: repeat(12, 1fr); grid-column-gap: 32px; grid-row-gap: 32px; transform: rotate(-20deg); opacity: 0.65; } .slider .item { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; } .slider .item .img { width: 100%; height: 100%; position: relative; will-change: transform; will-change: opacity; } .slider .item .img img { position: absolute; top: 0; width: 100%; height: 100%; object-fit: cover; position: relative; -webkit-filter: contrast(110%) brightness(110%) saturate(130%); filter: contrast(110%) brightness(110%) saturate(130%); } .slider .item .img img::before { content: ""; display: block; height: 100%; width: 100%; top: 0; left: 0; position: absolute; pointer-events: none; mix-blend-mode: screen; background: rgba(243, 106, 188, 0.3); } .slider .item .img-1 { grid-area: 1/1/7/5; } .slider .item .img-2 { grid-area: 2/5/7/13; } .slider .item .img-3 { grid-area: 7/1/12/9; } .slider .item .img-4 { grid-area: 7/9/13/13; } .slider .item .content { position: absolute; z-index: 2; top: 0; left: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; line-height: 1.15; font-size: 8rem; font-weight: 700; } .slider .item .content .wrap { text-align: center; text-shadow: 1px 1px 4px rgba(10, 9, 8, 0.2); width: 100%; max-width: 600px; line-height: 1; } .slider .item .content .wrap .letter { display: inline-block; } .slider .nav .next, .slider .nav .prev { height: 2rem; width: 2rem; position: absolute; top: calc(50% - 1rem); cursor: pointer; z-index: 3; transition: transform 0.3s; } .slider .nav .next { right: 2rem; background-image: url("data:image/svg+xml,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M 19 8 L 19 11 L 1 11 L 1 13 L 19 13 L 19 16 L 23 12 L 19 8 z' fill='white'/%3E%3C/svg%3E"); } .slider .nav .next:hover { transform: translateX(0.5rem); } .slider .nav .prev { left: 2rem; background-image: url("data:image/svg+xml,%3C?xml version='1.0' encoding='utf-8'?%3E %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M 5 8 L 1 12 L 5 16 L 5 13 L 23 13 L 23 11 L 5 11 L 5 8 z' fill='white'/%3E%3C/svg%3E"); } .slider .nav .prev:hover { transform: translateX(-0.5rem); } .slider .nav .explore-btn { z-index: 4; position: absolute; bottom: 2rem; left: calc(50% - 4rem); width: 8em; text-align: center; padding: 1rem 0; border: solid 2px white; background: transparent; color: white; transition: background-color 0.3s; cursor: pointer; } .slider .nav .explore-btn:hover { color: #0a0908; background: white; } .slider .item:not(.is-active) { opacity: 0; pointer-events: none; }
最後に、main.jsでスライダーのロジックを作成します。
// Import our custom CSS import "../scss/styles.scss"; // Import all of Bootstrap's JS import * as bootstrap from "bootstrap"; import anime from "animejs/lib/anime.es.js"; function init() { const slider = document.querySelector(".slider"); const nextBtn = slider.querySelector(".slider .nav .next"); const prevBtn = slider.querySelector(".slider .nav .prev"); const items = slider.querySelectorAll(".slider .item"); let current = 0; items.forEach((item) => { const textWrapper = item.querySelector(".wrap"); textWrapper.innerHTML = textWrapper.textContent.replace(/\S/g, "$&"); }); function anim(current, next, callback) { const currentImgs = current.querySelectorAll(".img"); const currentText = current.querySelectorAll(".content .letter"); const nextImgs = next.querySelectorAll(".img"); const nextText = next.querySelectorAll(".content .letter"); const duration = 400; const offset = "-=" + 300; const imgOffset = duration * 0.8; const tl = anime.timeline({ easing: "easeInOutQuint", duration: duration, complete: callback, }); // Add children tl.add({ targets: currentText, translateY: [0, "-.75em"], /*clipPath: ['polygon(0 0, 100% 0, 100% 100%, 0% 100%)', 'polygon(0 100%, 100% 100%, 100% 100%, 0% 100%)'],*/ opacity: [1, 0], easing: "easeInQuint", duration: 600, delay: (el, i) => 10 * (i + 1), }) .add( { targets: currentImgs[0], translateY: -600, rotate: [0, "-15deg"], opacity: [1, 0], easing: "easeInCubic", }, offset ) .add( { targets: currentImgs[1], translateY: -600, rotate: [0, "15deg"], opacity: [1, 0], easing: "easeInCubic", }, "-=" + imgOffset ) .add( { targets: currentImgs[2], translateY: -600, rotate: [0, "-15deg"], opacity: [1, 0], easing: "easeInCubic", }, "-=" + imgOffset ) .add( { targets: currentImgs[3], translateY: -600, rotate: [0, "15deg"], opacity: [1, 0], easing: "easeInCubic", }, "-=" + imgOffset ) .add({ targets: current, opacity: 0, duration: 10, easing: "easeInCubic", }) .add( { targets: next, opacity: 1, duration: 10, }, offset ) .add( { targets: nextImgs[0], translateY: [600, 0], rotate: ["15deg", 0], opacity: [0, 1], easing: "easeOutCubic", }, offset ) .add( { targets: nextImgs[1], translateY: [600, 0], rotate: ["-15deg", 0], opacity: [0, 1], easing: "easeOutCubic", }, "-=" + imgOffset ) .add( { targets: nextImgs[2], translateY: [600, 0], rotate: ["15deg", 0], opacity: [0, 1], easing: "easeOutCubic", }, "-=" + imgOffset ) .add( { targets: nextImgs[3], translateY: [600, 0], rotate: ["-15deg", 0], opacity: [0, 1], easing: "easeOutCubic", }, "-=" + imgOffset ) .add( { targets: nextText, translateY: [".75em", 0], /*clipPath: ['polygon(0 0, 100% 0, 100% 0, 0 0)','polygon(0 0, 100% 0, 100% 100%, 0% 100%)'],*/ opacity: [0, 1], easing: "easeOutQuint", duration: 600, delay: (el, i) => 10 * (i + 1), }, offset ); } let isPlaying = false; function updateSlider(newIndex) { const currentItem = items[current]; const newItem = items[newIndex]; function callback() { currentItem.classList.remove("is-active"); newItem.classList.add("is-active"); current = newIndex; isPlaying = false; } anim(currentItem, newItem, callback); } function next() { if (isPlaying) return; isPlaying = true; const newIndex = current === items.length - 1 ? 0 : current + 1; updateSlider(newIndex); } function prev() { if (isPlaying) return; isPlaying = true; const newIndex = current === 0 ? items.length - 1 : current - 1; updateSlider(newIndex); } nextBtn.onclick = next; prevBtn.onclick = prev; } document.addEventListener("DOMContentLoaded", init);
init()関数でブラウザがDOMContentLoadedのイベントを実行したときにイベントをマウントさせます。
この中でスライダーやナビゲーションのHTML要素を格納し、クリックイベントを実装します。
画像にdelay:の要素を加えていることに注目してください。
完成したコードはこちら(GitHub)からどうぞ。