GSAPで実現する高品質アニメーションまとめ|JavaScript表現・UI演出集

特集

GSAPで実現する高品質アニメーションまとめ|JavaScript表現・UI演出集

投稿日2026/05/04

更新日2026/4/22

Webサイトにおける「動き」は、ユーザー体験を大きく左右する重要な要素のひとつです。
中でもGSAPは、高いパフォーマンスと柔軟な制御が可能なアニメーションライブラリとして、多くの現場で採用されています。

本ページでは、GSAPを活用したさまざまなアニメーション事例や解説記事をまとめました。
スクロールに連動した演出や、洗練されたUIモーション、インタラクティブな表現など、すぐに取り入れたくなるアイデアを幅広く紹介しています。

「より滑らかで印象的な動きを実装したい」「ユーザーの体験をワンランク上げたい」と考えている方は、ぜひ参考にしてみてください。

ホバーで矢印と円が動く!円形マスクと一方向スライドのボタンアニメーション

<div class="kumonosu-section__wrap">
    <div class="kumonosu-button js-button">
        <a class="kumonosu-button__link" href="#">
            <div class="kumonosu-button__wrap">
                <span class="kumonosu-button__label js-button-label">VIEW ALL</span>
                <div class="kumonosu-arrow-wrapper">
                    <div class="kumonosu-arrow-circle-mask js-circle-mask">
                        <svg class="kumonosu-arrow-svg js-arrow-white" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20.828 12.343">
                            <line x1="1" y1="6.17" x2="18" y2="6.17" stroke-linecap="round"></line>
                            <path d="M14,1.17l5,5-5,5" stroke-linecap="round" stroke-linejoin="round"></path>
                        </svg>
                    </div>
                </div>
                <div class="kumonosu-button__border js-button-border"></div>
            </div>
        </a>
    </div>
</div>
:root {
	--kumonosu-primary: #6a5fed;
	--kumonosu-text: #222;
}

body {
	background-color: #f8f9fa;
	display: flex;
	justify-content: center;
	align-items: center;
	height: 100vh;
	margin: 0;
	font-family: "Helvetica Neue", Arial, "Hiragino Kaku Gothic ProN", "Hiragino Sans", Meiryo, sans-serif;
	-webkit-font-smoothing: antialiased;
}

.kumonosu-button {
	position: relative;
	display: inline-block;
	cursor: pointer;
	user-select: none;
}

.kumonosu-button__link {
	text-decoration: none;
	display: block;
	color: var(--kumonosu-text);
}

.kumonosu-button__wrap {
	position: relative;
	display: flex;
	align-items: center;
	padding: 0 0 12px 0;
}

.kumonosu-button__label {
	font-size: 16px;
	font-weight: 700;
	letter-spacing: 0.12em;
	margin-right: 50px;
	position: relative;
	z-index: 2;
}

.kumonosu-arrow-wrapper {
	position: relative;
	width: 48px;
	height: 48px;
	display: flex;
	justify-content: center;
	align-items: center;
}

.kumonosu-arrow-circle-mask {
	position: absolute;
	width: 48px;
	height: 48px;
	background-color: var(--kumonosu-primary);
	border-radius: 50%;
	overflow: hidden;
	z-index: 2;
	pointer-events: none;
	transform-origin: center center;
	display: flex;
	justify-content: center;
	align-items: center;
}

.kumonosu-arrow-svg {
	position: absolute;
	width: 20px;
	height: 12px;
	fill: none;
	stroke-width: 2.5px;
	stroke: #fff;
}

.kumonosu-button__border {
	position: absolute;
	bottom: 0;
	left: 0;
	width: 100%;
	height: 2px;
	background-color: var(--kumonosu-primary);
	transform: scaleX(0);
	transform-origin: left center;
}
window.onload = () => {
	const button = document.querySelector('.js-button');
	const label = document.querySelector('.js-button-label');
	const circleMask = document.querySelector('.js-circle-mask');
	const arrowWhite = document.querySelector('.js-arrow-white');
	const border = document.querySelector('.js-button-border');

	const duration = 0.5;
	const ease = "expo.inOut";

	// メインカラーを #6a5fed に更新
	const primaryColor = "#6a5fed";

	button.addEventListener('mouseenter', () => {
		// テキストと円の基本アニメーション
		gsap.to(label, {
			x: 10,
			color: primaryColor,
			duration: duration,
			ease: ease
		});
		gsap.to(circleMask, {
			scale: 0.15,
			duration: duration,
			ease: ease
		});
		gsap.to(border, {
			scaleX: 1,
			duration: duration,
			ease: ease,
			transformOrigin: "left"
		});

		// 矢印:中央から右へ消える
		gsap.killTweensOf(arrowWhite);
		gsap.set(arrowWhite, {
			x: 0,
			opacity: 1
		});
		gsap.to(arrowWhite, {
			x: 40,
			opacity: 0,
			duration: duration,
			ease: ease,
			immediateRender: false
		});
	});

	button.addEventListener('mouseleave', () => {
		// 基本状態へ戻す
		gsap.to(label, {
			x: 0,
			color: "#222",
			duration: duration,
			ease: ease
		});
		gsap.to(circleMask, {
			scale: 1,
			duration: duration,
			ease: ease
		});
		gsap.to(border, {
			scaleX: 0,
			duration: 0.4,
			ease: "power2.inOut",
			transformOrigin: "right"
		});

		// 矢印:左から中央へ現れる
		gsap.killTweensOf(arrowWhite);
		gsap.set(arrowWhite, {
			x: -40,
			opacity: 0
		});
		gsap.to(arrowWhite, {
			x: 0,
			opacity: 1,
			duration: duration,
			ease: ease,
			immediateRender: false
		});
	});
};
https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js
「GSAPの.set()メソッドによる状態の初期化」と「CSSのoverflowによる視覚的な切り取り」の組み合わせです。
これらを意識するだけで、標準的なWebサイトが一気にプロフェッショナルな仕上がりに変わります。

ぜひ、あなたのプロジェクトに取り入れてみてください!

CSSとJSで作る、スクロールで画像が迫ってくる3Dズーム演出

<div id="kumonosu-content">
	<main>
		<div class="kumonosu-container">
			<h1 class="kumonosu-heading">KUMONOSU Scroll</h1>
			<!-- 画像(Unsplash) -->
			<div class="kumonosu-item" data-layer="2"><img src="https://images.unsplash.com/photo-1470770841072-f978cf4d019e?auto=format&fit=crop&w=600&q=80" alt=""></div>
			<div class="kumonosu-item" data-layer="2"><img src="https://images.unsplash.com/photo-1441974231531-c6227db76b6e?auto=format&fit=crop&w=600&q=80" alt=""></div>
			<div class="kumonosu-item" data-layer="1"><img src="https://images.unsplash.com/photo-1501854140801-50d01698950b?auto=format&fit=crop&w=600&q=80" alt=""></div>
			<div class="kumonosu-item" data-layer="2"><img src="https://images.unsplash.com/photo-1464822759023-fed622ff2c3b?auto=format&fit=crop&w=600&q=80" alt=""></div>
			<div class="kumonosu-item" data-layer="3"><img src="https://images.unsplash.com/photo-1472214103451-9374bd1c798e?auto=format&fit=crop&w=600&q=80" alt=""></div>
			<div class="kumonosu-item" data-layer="1"><img src="https://images.unsplash.com/photo-1447752875215-b2761acb3c5d?auto=format&fit=crop&w=600&q=80" alt=""></div>
			<div class="kumonosu-item" data-layer="3"><img src="https://images.unsplash.com/photo-1506744038136-46273834b3fb?auto=format&fit=crop&w=600&q=80" alt=""></div>
			<div class="kumonosu-item" data-layer="3"><img src="https://images.unsplash.com/photo-1511497584788-876760111969?auto=format&fit=crop&w=600&q=80" alt=""></div>
			<div class="kumonosu-item" data-layer="1"><img src="https://images.unsplash.com/photo-1426604966848-d7adac402bff?auto=format&fit=crop&w=600&q=80" alt=""></div>
			<div class="kumonosu-item" data-layer="3"><img src="https://images.unsplash.com/photo-1414235077428-338989a2e8c0?auto=format&fit=crop&w=600&q=80" alt=""></div>
			<div class="kumonosu-item" data-layer="2"><img src="https://images.unsplash.com/photo-1500673922987-e212871fec22?auto=format&fit=crop&w=600&q=80" alt=""></div>
		</div>
		<section class="kumonosu-section-stick">
			<p class="kumonosu-reveal">KUMONOSUはコピペで使えるCSS・JSのアニメーションデザインをまとめたギャラリーサイトです。</p>
		</section>
	</main>
</div>
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@400;700&family=Roboto:wght@900&display=swap');
:root {
	--kumonosu-black: #0a0a0a;
	--kumonosu-white: #ffffff;
	--kumonosu-gray: #444444;
}
* {
	margin: 0;
	padding: 0;
	box-sizing: border-box;
}
html.lenis {
	height: auto;
}
.lenis-smooth {
	scroll-behavior: auto !important;
}
.lenis-smooth [data-lenis-prevent] {
	overscroll-behavior: contain;
}
.lenis-stopped {
	overflow: hidden;
}
body {
	background-color: var(--kumonosu-black);
	color: var(--kumonosu-white);
	font-family: "Noto Sans JP", "Roboto", sans-serif;
	overflow-x: hidden;
	line-height: 1.6;
}
.kumonosu-container {
	height: 100vh;
	overflow: hidden;
	position: relative;
	perspective: 1000px;
	width: 100vw;
}
.kumonosu-heading {
	font-family: 'Roboto', sans-serif;
	font-size: clamp(2rem, 8vw, 60px);
	font-weight: 900;
	text-align: center;
	opacity: 0.1;
	position: absolute;
	left: 50%;
	top: 50%;
	transform: translate(-50%, -50%);
	white-space: nowrap;
	z-index: 10;
	letter-spacing: -0.05em;
	text-transform: uppercase;
	will-change: transform, opacity;
}
.kumonosu-item {
	position: absolute;
	transform-origin: center center;
	will-change: transform, opacity;
}
.kumonosu-item[data-layer="3"] {
	opacity: 0.6;
	z-index: 3;
}
.kumonosu-item[data-layer="2"] {
	opacity: 0.4;
	z-index: 2;
}
.kumonosu-item[data-layer="1"] {
	opacity: 0.2;
	z-index: 1;
}
.kumonosu-item:nth-child(2) {
	left: 8vw;
	top: 20%;
	width: 14vw;
}
.kumonosu-item:nth-child(3) {
	left: 28%;
	top: 12%;
	width: 11vw;
}
.kumonosu-item:nth-child(4) {
	left: 42%;
	top: 5%;
	width: 13vw;
}
.kumonosu-item:nth-child(5) {
	right: 28%;
	top: 10%;
	width: 9vw;
}
.kumonosu-item:nth-child(6) {
	right: 6%;
	top: 22%;
	width: 15vw;
}
.kumonosu-item:nth-child(7) {
	bottom: 35%;
	right: 4%;
	width: 7vw;
}
.kumonosu-item:nth-child(8) {
	bottom: 12%;
	left: 15%;
	width: 12vw;
}
.kumonosu-item:nth-child(9) {
	bottom: 25%;
	left: 32%;
	width: 9vw;
}
.kumonosu-item:nth-child(10) {
	bottom: 48%;
	left: 6%;
	width: 8vw;
}
.kumonosu-item:nth-child(11) {
	bottom: 8%;
	right: 14%;
	width: 16vw;
}
.kumonosu-item:nth-child(12) {
	bottom: 15%;
	right: 38%;
	width: 10vw;
}
.kumonosu-item img {
	width: 100%;
	height: auto;
	display: block;
	filter: grayscale(20%);
}
.kumonosu-section-stick {
	min-height: 100vh;
	display: flex;
	justify-content: center;
	align-items: center;
	position: relative;
}
.kumonosu-reveal {
	font-size: clamp(1.4rem, 4.5vw, 3.2rem);
	font-weight: 700;
	text-align: center;
	width: 85%;
	line-height: 2;
	word-break: keep-all;
}
.char {
	display: inline-block;
}
gsap.registerPlugin(ScrollTrigger);
// 1. Lenis スムーズスクロールの初期化
const lenis = new Lenis();
lenis.on('scroll', ScrollTrigger.update);
gsap.ticker.add((time) => {
	lenis.raf(time * 1000);
});
gsap.ticker.lagSmoothing(0);
// 2. ズームアニメーション (GSAP Coreだけで可能)
const zoomTl = gsap.timeline({
	scrollTrigger: {
		trigger: ".kumonosu-container",
		start: "top top",
		end: "+=150%",
		pin: true,
		scrub: 1.2
	}
});
zoomTl.to(".kumonosu-item[data-layer='3']", {
	z: 800,
	opacity: 1
}, 0).to(".kumonosu-item[data-layer='2']", {
	z: 500,
	opacity: 1
}, 0).to(".kumonosu-item[data-layer='1']", {
	z: 200,
	opacity: 1
}, 0).to(".kumonosu-heading", {
	z: 100,
	opacity: 1
}, 0);
// 3. テキストリビール (SplitTypeを使用)
const textElement = document.querySelector('.kumonosu-reveal');
const splitText = new SplitType(textElement, {
	types: 'chars'
});
gsap.set(splitText.chars, {
	opacity: 0.1
});
const revealTl = gsap.timeline({
	scrollTrigger: {
		trigger: ".kumonosu-section-stick",
		pin: true,
		start: "center center",
		end: "+=2000",
		scrub: 0.5
	}
});
revealTl.to(splitText.chars, {
		opacity: 1,
		stagger: 0.1,
		ease: "none"
	}).to({}, {
		duration: 1
	}) // 溜め
	.to(textElement, {
		opacity: 0,
		scale: 1.05,
		filter: "blur(15px)",
		duration: 2,
		ease: "power2.in"
	});
https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js
https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js
https://cdn.jsdelivr.net/gh/studio-freight/lenis@1.0.19/bundled/lenis.min.js
https://unpkg.com/split-type
スクロール連動で複数の画像が奥から手前にぐっと迫ってくる3Dズーム演出です。

GSAP ScrollTrigger+CSS perspectiveによる没入感のあるヒーローセクションに加え、テキストが一文字ずつ現れるアニメーションも実装しています。

CSSとJSで作る、スクロールでサイバーな画面遷移が展開するヒーローセクション

<section class="kumonosu-main-section">
	<div class="kumonosu-wrapper-action">
		<div class="kumonosu-hero" style="--degrade: -150deg; --transparent: 100%; --black: 100%;">
			<picture>
				<img src="https://images.unsplash.com/photo-1519608487953-e999c86e7455?q=80&w=1200" alt="cyber city" class="kumonosu-pixel-loader">
			</picture>
			<div class="kumonosu-pixel-overlay"></div>
			<div class="kumonosu-hero-barcode-container">
				<img src="https://kumonosu.net/wp-content/themes/kumonosu/assets/img/common/logo.svg" alt="LOGO" loading="lazy" width="300" height="auto">
			</div>
		</div>
		<div class="kumonosu-content">
			<div class="kumonosu-content-before" style="--transparent3: 100%; --black3: 100%;"></div>
			<div class="kumonosu-content-after" style="--transparent2: 100%; --black2: 100%;">
				<div class="kumonosu-title">
					<h1>Friendly<br>Neighborhood</h1>
				</div>
				<div class="kumonosu-description">
					<div>
						<h2>KUMONOSU</h2>
						<p>KUMONOSUはコピペで使えるCSS・JSのアニメーションデザインをまとめたギャラリーサイトです。 </p>
					</div>
				</div>
			</div>
		</div>
	</div>
	<div class="kumonosu-gallery-section" style="--transparent4: 100%; --black4: 100%;">
		<div class="kumonosu-images">
			<picture>
				<img src="https://images.unsplash.com/photo-1550751827-4bd374c3f58b?q=80&w=1200" alt="cyber circuit" loading="lazy">
			</picture>
			<picture>
				<img src="https://images.unsplash.com/photo-1620641788421-7a1c342ea42e?q=80&w=1200" alt="cyber neon street" loading="lazy">
			</picture>
		</div>
	</div>
</section>
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400..900&display=swap');
* {
	margin: 0;
	padding: 0;
	box-sizing: border-box;
}
body {
	font-family: 'Arial', sans-serif;
	background: #000;
	color: #fff;
	overflow-x: hidden;
}
.kumonosu-container {
	max-width: 1200px;
	margin: 0 auto;
	padding: 0 20px;
}
.kumonosu-hero {
	position: relative;
	height: 100vh;
	display: flex;
	align-items: center;
	justify-content: center;
	background: #000;
	overflow: hidden;
	mask: linear-gradient(var(--degrade), transparent var(--transparent), black var(--black));
	-webkit-mask: linear-gradient(var(--degrade), transparent var(--transparent), black var(--black));
}
.kumonosu-hero picture {
	position: relative;
	display: block;
	width: 100%;
	height: 100%;
}
.kumonosu-hero picture img {
	width: 100%;
	height: 100%;
	object-fit: cover;
}
.kumonosu-hero picture:before {
	content: '';
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	background: linear-gradient(180deg, rgba(0, 0, 0, 0) 42%, rgba(0, 0, 0, 0.67) 94%);
	z-index: 1;
}
.kumonosu-section {
	position: relative;
	width: 100%;
	height: 100svh;
}
.kumonosu-pixel-loader {
	width: 100%;
	height: 100%;
	object-fit: cover;
	scale: 1.4;
	image-rendering: pixelated;
	image-rendering: -moz-crisp-edges;
	image-rendering: crisp-edges;
	filter: contrast(0%) saturate(0%);
}
.kumonosu-pixel-overlay {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	background:
		repeating-linear-gradient(0deg,
			transparent,
			transparent 3px,
			rgba(0, 0, 0, 0.3) 3px,
			rgba(0, 0, 0, 0.3) 6px),
		repeating-linear-gradient(90deg,
			transparent,
			transparent 3px,
			rgba(0, 0, 0, 0.3) 3px,
			rgba(0, 0, 0, 0.3) 6px);
	mix-blend-mode: multiply;
	opacity: 1;
}
.kumonosu-content {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	z-index: 1;
	overflow: hidden;
}
.kumonosu-content-after {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	z-index: 1;
	display: flex;
	flex-direction: column;
	align-items: center;
	mask: linear-gradient(var(--degrade2), transparent var(--transparent2), black var(--black2));
	-webkit-mask: linear-gradient(var(--degrade2), transparent var(--transparent2), black var(--black2));
	isolation: isolate;
}
.kumonosu-content-before {
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	width: 120%;
	height: 120%;
	background: #1b1b30;
	z-index: -1;
	mask: linear-gradient(-105deg, transparent var(--transparent3), black var(--black3));
	-webkit-mask: linear-gradient(-105deg, transparent var(--transparent3), black var(--black3));
}
.kumonosu-title {
	width: 100%;
	height: 80%;
	display: flex;
	align-items: center;
	justify-content: center;
	font-family: "Orbitron", sans-serif;
	;
	text-align: center;
}
.kumonosu-title h1 {
	font-size: clamp(2rem, 8vw, 6rem);
}
.kumonosu-description {
	width: 100%;
	height: 20%;
	padding-inline: 20px;
	display: flex;
	align-items: end;
	padding-bottom: 20px;
}
.kumonosu-description div {
	width: 50%;
}
.kumonosu-description div:nth-of-type(1) h2 {
	font-size: clamp(2rem, 4vw, 3rem);
	font-family: 'Montserrat', sans-serif;
	font-weight: 600;
	margin-bottom: 10px;
}
.kumonosu-description div:nth-of-type(1) p {
	font-size: clamp(1rem, 2vw, 1rem);
	font-family: 'Montserrat', sans-serif;
	font-weight: 400;
}
.kumonosu-gallery-section {
	position: absolute;
	top: 0;
	left: 50%;
	transform: translateX(-50%);
	width: 100vw;
	height: 100svh;
	background: #000;
	z-index: 2;
	mask: linear-gradient(105deg, transparent var(--transparent4), black var(--black4));
	-webkit-mask: linear-gradient(105deg, transparent var(--transparent4), black var(--black4));
	display: flex;
	align-items: center;
	justify-content: center;
}
.kumonosu-gallery-section .kumonosu-images {
	width: 100%;
	max-width: 1000px;
	height: 70vh;
	display: flex;
	justify-content: center;
	align-items: center;
	gap: 20px;
	padding: 0 20px;
}
.kumonosu-gallery-section .kumonosu-images picture {
	flex: 1;
	height: 100%;
	overflow: hidden;
}
.kumonosu-gallery-section .kumonosu-images img {
	width: 100%;
	height: 100%;
	object-fit: cover;
	opacity: 0;
	will-change: transform, opacity;
}
.kumonosu-hero-barcode-container {
	position: absolute;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	z-index: 10;
	opacity: 1;
	pointer-events: none;
	will-change: transform, opacity, scale;
}
document.addEventListener('DOMContentLoaded', () => {
	gsap.registerPlugin(ScrollTrigger);
	const lenis = new Lenis();
	lenis.on('scroll', ScrollTrigger.update);
	gsap.ticker.add((time) => {
		lenis.raf(time * 1000);
	});
	gsap.ticker.lagSmoothing(0);
	const mainSection = document.querySelector('.kumonosu-wrapper-action');
	const hero = document.querySelector('.kumonosu-hero');
	const content = document.querySelector('.kumonosu-content-after');
	const contentBefore = document.querySelector('.kumonosu-content-before');
	const gallerySection = document.querySelector('.kumonosu-gallery-section');
	const images = document.querySelectorAll('.kumonosu-gallery-section .kumonosu-images img');
	const heroBarcodeContainer = document.querySelector('.kumonosu-hero-barcode-container');
	// Initial setup animations
	gsap.to(hero, {
		'--black': '0%',
		'--transparent': '0%',
		'--degrade': '-90deg',
		duration: 2,
		ease: 'power2.out'
	});
	gsap.to('.kumonosu-pixel-loader', {
		filter: 'contrast(100%) saturate(100%)',
		scale: 1,
		duration: 2,
		ease: 'power2.out'
	});
	gsap.to('.kumonosu-pixel-overlay', {
		opacity: 0,
		duration: 2,
		ease: 'power2.out',
		delay: 1
	});
	// 自動で動くロゴのfromToアニメーションを削除しました
	const mostrarGaleria = () => {
		if (gallerySection.classList.contains('kumonosu-animated')) return;
		gallerySection.classList.add('kumonosu-animated');
		gsap.fromTo(images, {
			x: 100,
			opacity: 0
		}, {
			x: 0,
			opacity: 1,
			duration: 1.5,
			stagger: 0.3,
			ease: 'power2.out',
			delay: 0.2
		});
	};
	const ocultarGaleria = () => {
		if (!gallerySection.classList.contains('kumonosu-animated')) return;
		gallerySection.classList.remove('kumonosu-animated');
		gsap.to(images, {
			opacity: 0,
			x: 100,
			duration: 0.8,
			stagger: 0.1,
			ease: 'power2.in'
		});
	};
	ScrollTrigger.create({
		trigger: '.kumonosu-main-section',
		start: 'top top',
		end: `+=${window.innerHeight * 3}px`,
		pin: true,
		scrub: 1,
		onUpdate: (self) => {
			const progress = self.progress;
			if (progress < 0.2) {
				gsap.to(mainSection, {
					scale: 1 - (progress * 0.8)
				});
			} else {
				gsap.to(mainSection, {
					scale: 0.8
				});
			}
			// スクロール進行度に合わせてロゴを消す(スクロール操作のみに連動)
			if (progress > 0.15) {
				gsap.to(heroBarcodeContainer, {
					opacity: 0,
					duration: 0.5,
					ease: 'power2.out'
				});
			} else {
				gsap.to(heroBarcodeContainer, {
					opacity: 1,
					duration: 0.5,
					ease: 'power2.out'
				});
			}
			if (progress > 0.3) {
				gsap.to(content, {
					'--degrade2': '0deg',
					'--transparent2': '0%',
					'--black2': '0%',
					duration: 1.5,
					ease: 'power2.out'
				});
			} else {
				gsap.to(content, {
					'--degrade2': '-150deg',
					'--transparent2': '100%',
					'--black2': '100%',
					duration: 1.5,
					ease: 'power2.out'
				});
			}
			if (progress > 0.6) {
				gsap.to(contentBefore, {
					'--transparent3': '0%',
					'--black3': '0%',
					duration: 2,
					ease: 'power2.out'
				});
			} else {
				gsap.to(contentBefore, {
					'--transparent3': '100%',
					'--black3': '100%',
					duration: 2,
					ease: 'power2.out'
				});
			}
			if (progress > 0.85) {
				gsap.to(gallerySection, {
					'--transparent4': '0%',
					'--black4': '0%',
					duration: 2,
					ease: 'power2.out'
				});
			} else {
				gsap.to(gallerySection, {
					'--transparent4': '100%',
					'--black4': '100%',
					duration: 2,
					ease: 'power2.out'
				});
			}
			if (progress > 0.9) {
				mostrarGaleria();
			} else {
				ocultarGaleria();
			}
		},
	});
});
https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js
https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/ScrollTrigger.min.js
https://unpkg.com/lenis@1.1.20/dist/lenis.min.js
スクロールに連動して、背景・ロゴ・説明・ギャラリーが段階的に“斜めマスク”で切り替わるサイバー演出。

CSSとJSで作る、ガラス風UIの展開式Dockナビゲーション

<div class="kumonosu-wrapper">
	<aside class="kumonosu-dock" id="kumonosu-dock">
		<div class="kumonosu-panel-stack">
			<!-- メニューパネル -->
			<section class="kumonosu-panel" data-panel="menu">
				<div class="kumonosu-item">
					<div class="kumonosu-text-group">
						<div class="kumonosu-title">サイトについて</div>
						<div class="kumonosu-subtitle">活動内容やビジョンのご紹介</div>
					</div>
				</div>
				<div class="kumonosu-item">
					<div class="kumonosu-text-group">
						<div class="kumonosu-title">お問い合わせ</div>
						<div class="kumonosu-subtitle">フォームよりお気軽にご連絡ください</div>
					</div>
				</div>
			</section>
			<!-- カテゴリパネル -->
			<section class="kumonosu-panel" data-panel="category">
				<div class="kumonosu-item">
					<div class="kumonosu-icon-ghost"><i data-lucide="folder"></i></div>
					<div class="kumonosu-text-group">
						<div class="kumonosu-title">カテゴリ1</div>
						<div class="kumonosu-subtitle">最新のニュースとトピック</div>
					</div>
					<div class="kumonosu-tag">最新</div>
				</div>
				<div class="kumonosu-item">
					<div class="kumonosu-icon-ghost"><i data-lucide="folder"></i></div>
					<div class="kumonosu-text-group">
						<div class="kumonosu-title">カテゴリ2</div>
						<div class="kumonosu-subtitle">深く掘り下げた特集記事</div>
					</div>
				</div>
			</section>
			<!-- タグパネル -->
			<section class="kumonosu-panel" data-panel="tags">
				<div class="kumonosu-item">
					<div class="kumonosu-icon-ghost"><i data-lucide="hash"></i></div>
					<div class="kumonosu-text-group">
						<div class="kumonosu-title">タグ1</div>
						<div class="kumonosu-subtitle">注目度の高いキーワード</div>
					</div>
				</div>
				<div class="kumonosu-item">
					<div class="kumonosu-icon-ghost"><i data-lucide="hash"></i></div>
					<div class="kumonosu-text-group">
						<div class="kumonosu-title">タグ2</div>
						<div class="kumonosu-subtitle">過去のアーカイブ一覧</div>
					</div>
				</div>
			</section>
		</div>
		<div class="kumonosu-divider"></div>
		<nav class="kumonosu-nav">
			<span class="kumonosu-pill" id="kumonosu-pill"></span>
			<button class="kumonosu-tab" data-panel="menu">
				<i data-lucide="menu"></i><span>メニュー</span>
			</button>
			<button class="kumonosu-tab" data-panel="category">
				<i data-lucide="grid"></i><span>カテゴリ</span>
			</button>
			<button class="kumonosu-tab" data-panel="tags">
				<i data-lucide="tag"></i><span>タグ</span>
			</button>
		</nav>
	</aside>
</div>
@layer reset {
	*, *::before, *::after {
		box-sizing: border-box;
	}
	html {
		-moz-text-size-adjust: none;
		-webkit-text-size-adjust: none;
		text-size-adjust: none;
	}
	body {
		margin: 0;
		min-block-size: 100vh;
		line-height: 1.6;
		background: #0a0a0a;
		color: #fff;
		overflow-x: hidden;
	}
	button {
		font: inherit;
		cursor: pointer;
		border: none;
		background: none;
		color: inherit;
	}
}
:root {
	/* 接頭辞をつけた変数名で定義 */
	--kumonosu-bg-dock: rgba(25, 25, 25, 0.75);
	--kumonosu-glass-border: rgba(255, 255, 255, 0.12);
	--kumonosu-text-main: #ffffff;
	--kumonosu-text-sub: #a0a0a0;
	--kumonosu-tab-idle: #808080;
	--kumonosu-tab-active: #000000;
	--kumonosu-pill: #ffffff;
	--kumonosu-hover: rgba(255, 255, 255, 0.08);
	--kumonosu-font-base: "Inter", "Hiragino Kaku Gothic ProN", "Hiragino Sans", sans-serif;
	--kumonosu-radius-dock: 28px;
	--kumonosu-radius-item: 16px;
	--kumonosu-duration: 0.35s;
	--kumonosu-ease: cubic-bezier(0.2, 0.8, 0.2, 1);
}
.kumonosu-wrapper {
	display: flex;
	align-items: flex-end;
	justify-content: center;
	min-height: 100vh;
	padding-bottom: 4rem;
	font-family: var(--kumonosu-font-base);
	background: radial-gradient(circle at center, #1a1a1a 0%, #050505 100%);
}
/* ドックのメインコンテナ */
.kumonosu-dock {
	background: var(--kumonosu-bg-dock);
	backdrop-filter: blur(20px) saturate(180%);
	-webkit-backdrop-filter: blur(20px) saturate(180%);
	border: 1px solid var(--kumonosu-glass-border);
	border-radius: var(--kumonosu-radius-dock);
	padding: 6px;
	display: flex;
	flex-direction: column;
	position: relative;
	transition: gap var(--kumonosu-duration) var(--kumonosu-ease);
	box-shadow: 0 25px 50px rgba(0, 0, 0, 0.5);
	z-index: 10;
}
/* 背景拡張 */
.kumonosu-dock::before {
	content: "";
	background: var(--kumonosu-bg-dock);
	backdrop-filter: blur(20px);
	border: 1px solid var(--kumonosu-glass-border);
	border-radius: var(--kumonosu-radius-dock);
	inset: 0;
	position: absolute;
	transition: inset 0.4s var(--kumonosu-ease);
	z-index: -1;
}
.kumonosu-dock.kumonosu-is-open {
	gap: 12px;
}
.kumonosu-dock.kumonosu-is-open::before {
	inset: -1.5rem -1.2rem -0.2rem -1.2rem;
}
.kumonosu-panel-stack {
	overflow: hidden;
	width: 360px;
	position: relative;
}
.kumonosu-panel {
	height: 0;
	opacity: 0;
	pointer-events: none;
	transform: translateY(10px);
	transition: height 0.4s var(--kumonosu-ease), opacity 0.3s, transform 0.3s;
	display: flex;
	flex-direction: column;
}
.kumonosu-panel.kumonosu-is-active {
	height: auto;
	opacity: 1;
	pointer-events: auto;
	transform: translateY(0);
	padding: 8px 0;
}
.kumonosu-item {
	display: flex;
	align-items: center;
	gap: 1rem;
	padding: 12px 16px;
	border-radius: var(--kumonosu-radius-item);
	position: relative;
	cursor: pointer;
}
.kumonosu-item-hover-indicator {
	background: var(--kumonosu-hover);
	border-radius: var(--kumonosu-radius-item);
	position: absolute;
	z-index: -1;
	opacity: 0;
	pointer-events: none;
}
.kumonosu-icon-ghost {
	width: 24px;
	height: 24px;
	display: flex;
	align-items: center;
	justify-content: center;
	color: var(--kumonosu-text-sub);
	transition: transform 0.3s var(--kumonosu-ease);
	flex-shrink: 0;
}
.kumonosu-item:hover .kumonosu-icon-ghost,
.kumonosu-item:hover .kumonosu-text-group {
	transform: translateX(8px);
}
.kumonosu-text-group {
	display: flex;
	flex-direction: column;
	transition: transform 0.3s var(--kumonosu-ease);
}
.kumonosu-title {
	font-weight: 600;
	color: var(--kumonosu-text-main);
	font-size: 1rem;
	line-height: 1.2;
}
.kumonosu-subtitle {
	font-size: 0.8rem;
	color: var(--kumonosu-text-sub);
	margin-top: 4px;
}
.kumonosu-tag {
	margin-left: auto;
	border: 1px solid var(--kumonosu-glass-border);
	color: var(--kumonosu-text-sub);
	font-size: 0.7rem;
	padding: 2px 8px;
	border-radius: 10px;
	background: rgba(255, 255, 255, 0.05);
	flex-shrink: 0;
}
.kumonosu-divider {
	background: var(--kumonosu-glass-border);
	height: 0;
	opacity: 0;
	margin: 0 10px;
	transition: height 0.3s, opacity 0.3s;
}
.kumonosu-dock.kumonosu-is-open .kumonosu-divider {
	height: 1px;
	opacity: 1;
}
.kumonosu-nav {
	display: grid;
	grid-template-columns: repeat(3, 1fr);
	gap: 4px;
	position: relative;
}
.kumonosu-pill {
	background: var(--kumonosu-pill);
	border-radius: 14px;
	position: absolute;
	height: calc(100% - 4px);
	top: 2px;
	z-index: 0;
	opacity: 0;
	pointer-events: none;
}
.kumonosu-tab {
	display: flex;
	align-items: center;
	justify-content: center;
	gap: 8px;
	padding: 10px 0;
	color: var(--kumonosu-tab-idle);
	font-weight: 600;
	font-size: 0.9rem;
	position: relative;
	z-index: 1;
	transition: color 0.3s;
}
.kumonosu-tab.kumonosu-is-active {
	color: var(--kumonosu-tab-active);
}
.kumonosu-tab i {
	width: 16px;
	height: 16px;
	stroke-width: 2.5px;
}
class KumonosuDockNavigation {
	constructor() {
		this.state = {
			activePanelId: null,
			isOpen: false
		};
		this.elements = {};
		this.panelHoverState = new Map();
		this.init();
	}
	init() {
		lucide.createIcons();
		this.cacheElements();
		this.setupPanels();
		this.bindEvents();
	}
	cacheElements() {
		this.elements = {
			dock: document.getElementById('kumonosu-dock'),
			pill: document.getElementById('kumonosu-pill'),
			tabs: Array.from(document.querySelectorAll('.kumonosu-tab')),
			panels: Array.from(document.querySelectorAll('.kumonosu-panel')),
			panelsById: {}
		};
		this.elements.panels.forEach(p => {
			this.elements.panelsById[p.dataset.panel] = p;
		});
	}
	setupPanels() {
		this.elements.panels.forEach(panel => {
			const indicator = document.createElement("span");
			indicator.className = "kumonosu-item-hover-indicator";
			panel.prepend(indicator);
			const hoverEntry = {
				indicator,
				activeItem: null
			};
			this.panelHoverState.set(panel, hoverEntry);
			panel.addEventListener("pointerover", (e) => {
				const item = e.target.closest('.kumonosu-item');
				if (item && panel.contains(item)) this.renderHoverIndicator(panel, item);
			});
			panel.addEventListener("pointerleave", () => {
				gsap.to(indicator, {
					opacity: 0,
					duration: 0.2
				});
				hoverEntry.activeItem = null;
			});
		});
	}
	bindEvents() {
		this.elements.tabs.forEach(tab => {
			tab.addEventListener("click", () => this.handleTabSelect(tab.dataset.panel));
		});
		document.addEventListener("pointerdown", (e) => {
			if (this.state.isOpen && !this.elements.dock.contains(e.target)) {
				this.closeMenu();
			}
		});
	}
	handleTabSelect(panelId) {
		if (this.state.isOpen && this.state.activePanelId === panelId) {
			this.closeMenu();
		} else {
			this.openMenu(panelId);
		}
	}
	openMenu(panelId) {
		this.state.isOpen = true;
		this.state.activePanelId = panelId;
		this.updateUI();
	}
	closeMenu() {
		this.state.isOpen = false;
		this.state.activePanelId = null;
		this.updateUI();
	}
	updateUI() {
		const {
			dock,
			tabs,
			pill,
			panelsById
		} = this.elements;
		// クラス名に接頭辞を使用
		dock.classList.toggle('kumonosu-is-open', this.state.isOpen);
		tabs.forEach(tab => {
			const isActive = this.state.isOpen && tab.dataset.panel === this.state.activePanelId;
			tab.classList.toggle('kumonosu-is-active', isActive);
			if (isActive) {
				gsap.to(pill, {
					left: tab.offsetLeft,
					width: tab.offsetWidth,
					opacity: 1,
					duration: 0.3,
					ease: "power3.inOut"
				});
			}
		});
		if (!this.state.isOpen) gsap.to(pill, {
			opacity: 0,
			duration: 0.2
		});
		Object.keys(panelsById).forEach(id => {
			panelsById[id].classList.toggle('kumonosu-is-active', this.state.isOpen && id === this.state.activePanelId);
		});
	}
	renderHoverIndicator(panel, item) {
		const entry = this.panelHoverState.get(panel);
		if (!entry || entry.activeItem === item) return;
		entry.activeItem = item;
		gsap.to(entry.indicator, {
			top: item.offsetTop,
			left: item.offsetLeft,
			width: item.offsetWidth,
			height: item.offsetHeight,
			opacity: 1,
			duration: 0.25,
			ease: "power2.out"
		});
	}
}
// 初期化
new KumonosuDockNavigation();
https://unpkg.com/lucide@latest
https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.2/gsap.min.js
ガラス風デザインを採用したDock型ナビゲーションUIです。

タブをクリックするとパネルが展開し、カテゴリやタグなどの情報をスムーズに切り替え表示できます。
GSAPによるアニメーションとCSSのblur・backdrop-filterを組み合わせることで、軽量ながらアプリライクな操作感を実現。

ブログ、ポートフォリオ、管理画面など幅広いサイト構成に応用できるナビゲーションコンポーネントです。

CSSとJSだけで作る、3Dフリップがめくれるスクロールアニメーション

<div class="kumonosu-hero" id="kumonosu-hero-section">
	<div class="kumonosu-grid" id="kumonosu-grid-container"></div>
</div>
:root {
	--kumonosu-bg-dark: #000;
}
body, html {
	margin: 0;
	padding: 0;
	background-color: var(--kumonosu-bg-dark);
	overflow: hidden;
	width: 100%;
	height: 100%;
}
.kumonosu-hero {
	position: fixed;
	top: 0;
	left: 0;
	width: 100vw;
	height: 100vh;
	z-index: 10;
	perspective: 1200px;
}
.kumonosu-grid {
	display: grid;
	width: 100vw;
	height: 100vh;
	gap: 3px;
	transform-style: preserve-3d;
	transform: translateZ(-20px);
	will-change: gap, transform;
}
.kumonosu-panel {
	position: relative;
	transform-style: preserve-3d;
	will-change: transform;
}
.kumonosu-panel-item {
	position: absolute;
	top: 0;
	left: 0;
	width: 100%;
	height: 100%;
	backface-visibility: hidden;
	border-radius: 10px;
	background-repeat: no-repeat;
	will-change: border-radius;
}
.kumonosu-panel-item[data-panel-item="front"] {
	transform: rotateX(0deg);
}
.kumonosu-panel-item[data-panel-item="back"] {
	transform: rotateX(180deg);
}
const KUMONOSU_CONFIG = {
	pc: {
		front: 'https://kumonosu.net/wp-content/uploads/2026/04/260408a.jpg.webp',
		back: 'https://kumonosu.net/wp-content/uploads/2026/04/260408b.jpg.webp',
		cols: 6,
		rows: 6,
		aspect: 1536 / 975
	},
	sp: {
		front: 'https://kumonosu.net/wp-content/uploads/2026/04/260408a_sp.jpg.webp',
		back: 'https://kumonosu.net/wp-content/uploads/2026/04/260408b_sp.jpg.webp',
		cols: 3,
		rows: 6,
		aspect: 750 / 1334
	},
	breakpoint: 768
};
const gridContainer = document.getElementById('kumonosu-grid-container');
let kumonosuPanels = [];
let kumonosuFlipTl;
let kumonosuIsFlipped = false;

function initKumonosuApp() {
	if (kumonosuFlipTl) kumonosuFlipTl.kill();
	gridContainer.innerHTML = '';
	kumonosuPanels = [];
	const vw = window.innerWidth;
	const vh = window.innerHeight;
	const isMobile = vw <= KUMONOSU_CONFIG.breakpoint;
	const mode = isMobile ? KUMONOSU_CONFIG.sp : KUMONOSU_CONFIG.pc;
	gridContainer.style.gridTemplateColumns = `repeat(${mode.cols}, 1fr)`;
	gridContainer.style.gridTemplateRows = `repeat(${mode.rows}, 1fr)`;
	const imgAspect = mode.aspect;
	const winAspect = vw / vh;
	let bgSizeW, bgSizeH;
	if (winAspect > imgAspect) {
		bgSizeW = vw;
		bgSizeH = vw / imgAspect;
	} else {
		bgSizeW = vh * imgAspect;
		bgSizeH = vh;
	}
	const offsetX = (vw - bgSizeW) / 2;
	const offsetY = (vh - bgSizeH) / 2;
	for (let i = 0; i < mode.rows * mode.cols; i++) {
		const panel = document.createElement('div');
		panel.className = 'kumonosu-panel';
		const colIndex = i % mode.cols;
		const rowIndex = Math.floor(i / mode.cols);
		// 各タイルのピクセルサイズと座標を精密に計算
		const tileW = vw / mode.cols;
		const tileH = vh / mode.rows;
		const posX = colIndex * tileW;
		const posY = rowIndex * tileH;
		const bgX = -(posX - offsetX);
		const bgY = -(posY - offsetY);
		panel.innerHTML = `
                    <div class="kumonosu-panel-item" data-panel-item="front" 
                         style="background-image: url(${mode.front}); background-size: ${bgSizeW}px ${bgSizeH}px; background-position: ${bgX}px ${bgY}px;"></div>
                    <div class="kumonosu-panel-item" data-panel-item="back" 
                         style="background-image: url(${mode.back}); background-size: ${bgSizeW}px ${bgSizeH}px; background-position: ${bgX}px ${bgY}px;"></div>
                `;
		gridContainer.appendChild(panel);
		kumonosuPanels.push(panel);
	}
	buildKumonosuTimeline();
	if (kumonosuIsFlipped) {
		gsap.set(kumonosuPanels, {
			rotateX: -180,
			scale: 1.01
		}); // めくれた状態では少し拡大して隙間を埋める
		gsap.set(gridContainer, {
			gap: 0,
			z: 0
		});
		gsap.set(".kumonosu-panel-item", {
			borderRadius: 0
		});
		kumonosuFlipTl.progress(1);
	}
}

function buildKumonosuTimeline() {
	kumonosuFlipTl = gsap.timeline({
		paused: true,
		onComplete: () => {
			kumonosuIsFlipped = true;
		},
		onReverseComplete: () => {
			kumonosuIsFlipped = false;
		}
	});
	// 1. 回転アニメーション
	kumonosuFlipTl.to(kumonosuPanels, {
		duration: 1.3,
		rotateX: -180,
		ease: "elastic.out(0.8, 0.4)",
		stagger: {
			each: 0.006,
			from: "random"
		}
	}, 0);
	// 2. 隙間、奥行きの解消 + 【重要】スケールをわずかに大きくして隙間を埋める
	kumonosuFlipTl.to(gridContainer, {
		gap: 0,
		z: 0,
		duration: 0.2,
		ease: "power3.inOut"
	}, 0.4);
	// 3. タイル自体を1%大きくして、隣と重ねることで境界線の隙間を消す
	kumonosuFlipTl.to(kumonosuPanels, {
		scale: 1.01, // 1.01倍にすることで物理的な隙間を覆い隠す
		duration: 0.2
	}, 0.4);
	// 4. 角の丸みを消す
	kumonosuFlipTl.to(".kumonosu-panel-item", {
		borderRadius: 0,
		duration: 0.1
	}, 0.6);
}
initKumonosuApp();
let kumonosuResizeTimer;
window.addEventListener('resize', () => {
	clearTimeout(kumonosuResizeTimer);
	kumonosuResizeTimer = setTimeout(initKumonosuApp, 200);
});
const handleKumonosuWheel = (e) => {
	if (e.deltaY > 0 && !kumonosuIsFlipped) kumonosuFlipTl.play();
	else if (e.deltaY < 0 && kumonosuIsFlipped) kumonosuFlipTl.reverse();
};
window.addEventListener('wheel', handleKumonosuWheel, {
	passive: false
});
let kumonosuTouchStart = 0;
window.addEventListener('touchstart', (e) => {
	kumonosuTouchStart = e.touches[0].clientY;
}, {
	passive: true
});
window.addEventListener('touchmove', (e) => {
	const touchEnd = e.touches[0].clientY;
	const diff = kumonosuTouchStart - touchEnd;
	if (diff > 10 && !kumonosuIsFlipped) kumonosuFlipTl.play();
	else if (diff < -10 && kumonosuIsFlipped) kumonosuFlipTl.reverse();
}, {
	passive: false
});
https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js
画面全体をタイル状に分割し、それぞれが立体的にフリップして別画像へ切り替わるヒーローアニメーションUIです。
CSSのperspectiveによる3D表現とGSAPのstaggerアニメーションを組み合わせることで、ランダム性のあるダイナミックな演出を実現しています。

スクロール操作に連動してアニメーションが再生・逆再生されるため、ユーザー操作と視覚体験が自然に結びつくのが特徴です。
PCとスマホで画像比率・分割数を切り替える設計になっており、レスポンシブ対応のヒーローセクション実装の参考としても活用できます。