HTML / CSS / JS
スクロール
2026/09/07
2026/9/6
ページを開くとカードが上空から次々と降ってきて横一列に並び、マウスを動かせば奥行きに応じてカードが揺れ、スクロールすると放射状に散らばっていく――そんな動きの密度が高いヒーローセクションを、GSAPとCSSで実装したデモです。
各カードにはdata属性で傾き(rot)と奥行き(depth)が設定されており、イントロ・フロート・パララックス・ホバー3Dチルト・スクロール散開と、5種類のアニメーションが自然に重なり合います。
背景にはSVGベースのグレインテクスチャをオーバーレイし、ざらっとした質感がデザインに奥行きを加えています。
ポートフォリオやクリエイティブ系サイトのファーストビューに最適です。
<div class="kumonosu-grain"></div>
<section class="kumonosu-hero">
<div class="kumonosu-cards-row" id="kumonosu-cards-container">
<div class="kumonosu-card kumonosu-card-1" data-rot="-9" data-depth="14">
<img src="https://kumonosu.net/wp-content/uploads/2026/01/260114c.jpeg.webp" alt="" />
</div>
<div class="kumonosu-card kumonosu-card-2" data-rot="-5" data-depth="10">
<img src="https://kumonosu.net/wp-content/uploads/2026/01/176a.jpg.webp" alt="" />
</div>
<div class="kumonosu-card kumonosu-card-3" data-rot="-2" data-depth="8">
<img src="https://kumonosu.net/wp-content/uploads/2026/01/176b.jpg.webp" alt="" />
</div>
<div class="kumonosu-card kumonosu-card-4" data-rot="3" data-depth="12">
<img src="https://kumonosu.net/wp-content/uploads/2026/01/176c.jpg.webp" alt="" />
</div>
<div class="kumonosu-card kumonosu-card-5" data-rot="0" data-depth="6">
<img src="https://kumonosu.net/wp-content/uploads/2026/01/176f.jpg.webp" alt="" />
</div>
<div class="kumonosu-card kumonosu-card-6" data-rot="4" data-depth="11">
<img src="https://kumonosu.net/wp-content/uploads/2026/01/176e.jpg.webp" alt="" />
</div>
<div class="kumonosu-card kumonosu-card-7" data-rot="7" data-depth="9">
<img src="https://kumonosu.net/wp-content/uploads/2026/01/176g.jpg.webp" alt="" />
</div>
<div class="kumonosu-card kumonosu-card-8" data-rot="-4" data-depth="13">
<img src="https://kumonosu.net/wp-content/uploads/2026/01/260114c.jpeg.webp" alt="" />
</div>
</div>
</section>
:root {
--kumonosu-bg: #f5efe2;
--kumonosu-ink: #141416;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
background: var(--kumonosu-bg);
color: var(--kumonosu-ink);
font-family: "DM Sans", system-ui, sans-serif;
-webkit-font-smoothing: antialiased;
}
body {
background: radial-gradient(120% 70% at 50% 0%,
#f8f3e7 0%,
#f2ead6 60%,
#e9dec3 100%);
min-height: 200vh;
}
.kumonosu-grain {
position: fixed;
inset: 0;
pointer-events: none;
z-index: 100;
opacity: 0.06;
mix-blend-mode: multiply;
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScyMjAnIGhlaWdodD0nMjIwJz48ZmlsdGVyIGlkPSduJz48ZmVUdXJidWxlbmNlIHR5cGU9J2ZyYWN0YWxOb2lzZScgYmFzZUZyZXF1ZW5jeT0nMC44NScgbnVtT2N0YXZlcz0nMicgc3RpdGNoVGlsZXM9J3N0aXRjaCcvPjxmZUNvbG9yTWF0cml4IHZhbHVlcz0nMCAwIDAgMCAwLjEgIDAgMCAwIDAgMC4xICAwIDAgMCAwIDAuMSAgMCAwIDAgMC42IDAnLz48L2ZpbHRlcj48cmVjdCB3aWR0aD0nMTAwJScgaGVpZ2h0PScxMDAlJyBmaWx0ZXI9J3VybCgjbiknLz48L3N2Zz4=");
}
.kumonosu-hero {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
display: flex;
align-items: center;
justify-content: center;
z-index: 5;
overflow: hidden;
pointer-events: none;
}
.kumonosu-cards-row {
position: relative;
width: 100%;
height: 320px;
z-index: 3;
pointer-events: none;
}
.kumonosu-cards-row>* {
pointer-events: auto;
}
.kumonosu-card {
position: absolute;
border-radius: 18px;
overflow: hidden;
background: #d1cbc0;
box-shadow: 0 30px 60px -12px rgba(0, 0, 0, 0.3),
0 18px 36px -18px rgba(0, 0, 0, 0.4),
inset 0 1px 0 rgba(255, 255, 255, 0.25);
cursor: pointer;
will-change: transform;
transition: box-shadow 0.5s cubic-bezier(0.6, 0, 0.2, 1);
}
.kumonosu-card img {
width: 100%;
height: 100%;
object-fit: cover;
object-position: center top;
display: block;
}
.kumonosu-card:hover {
box-shadow: 0 50px 80px -20px rgba(0, 0, 0, 0.4),
0 30px 50px -30px rgba(0, 0, 0, 0.5),
inset 0 1px 0 rgba(255, 255, 255, 0.3);
}
.kumonosu-card-1 {
width: 130px;
height: 180px;
left: 4%;
top: 30px;
}
.kumonosu-card-2 {
width: 160px;
height: 220px;
left: 12%;
top: 50px;
}
.kumonosu-card-3 {
width: 200px;
height: 270px;
left: 22%;
top: 20px;
}
.kumonosu-card-4 {
width: 150px;
height: 200px;
left: 36%;
top: 70px;
}
.kumonosu-card-5 {
width: 230px;
height: 310px;
left: 44%;
top: 0px;
}
.kumonosu-card-6 {
width: 160px;
height: 215px;
left: 59%;
top: 55px;
}
.kumonosu-card-7 {
width: 175px;
height: 240px;
left: 70%;
top: 30px;
}
.kumonosu-card-8 {
width: 130px;
height: 175px;
left: 84%;
top: 50px;
}
@media (max-width: 750px) {
.kumonosu-cards-row {
height: 220px;
}
.kumonosu-card-1 {
width: 80px;
height: 110px;
left: 0%;
top: 20px;
}
.kumonosu-card-2 {
width: 90px;
height: 130px;
left: 10%;
top: 35px;
}
.kumonosu-card-3 {
width: 110px;
height: 150px;
left: 22%;
top: 15px;
}
.kumonosu-card-4 {
width: 90px;
height: 120px;
left: 36%;
top: 50px;
}
.kumonosu-card-5 {
width: 130px;
height: 180px;
left: 46%;
top: 0;
}
.kumonosu-card-6 {
width: 95px;
height: 130px;
left: 62%;
top: 40px;
}
.kumonosu-card-7 {
width: 100px;
height: 140px;
left: 74%;
top: 20px;
}
.kumonosu-card-8 {
width: 80px;
height: 110px;
left: 87%;
top: 35px;
}
}
gsap.registerPlugin(ScrollTrigger);
// INITIAL STATES
document.querySelectorAll(".kumonosu-card").forEach((card) => {
const rot = parseFloat(card.dataset.rot) || 0;
card.dataset.restRot = rot;
gsap.set(card, {
y: -800,
rotation: rot + 25,
opacity: 0,
scale: 0.7
});
});
// INTRO TIMELINE
gsap.to(".kumonosu-card", {
y: 0,
opacity: 1,
scale: 1,
rotation: (i, el) => parseFloat(el.dataset.restRot) || 0,
duration: 1.2,
stagger: {
each: 0.1,
from: "center"
},
ease: "back.out(1.4)",
delay: 0.2
});
// FLOAT
document.querySelectorAll(".kumonosu-card").forEach((card, i) => {
const rot = parseFloat(card.dataset.restRot) || 0;
gsap.to(card, {
y: `+=${10 + (i % 3) * 5}`,
rotation: rot + (i % 2 === 0 ? 1.5 : -1.5),
duration: 3 + (i % 4) * 0.5,
delay: 1.5 + i * 0.1,
ease: "sine.inOut",
yoyo: true,
repeat: -1
});
});
// PARALLAX
let kumonosu_mx = 0,
kumonosu_my = 0,
kumonosu_tx = 0,
kumonosu_ty = 0;
window.addEventListener("mousemove", (e) => {
kumonosu_mx = (e.clientX / window.innerWidth - 0.5) * 2;
kumonosu_my = (e.clientY / window.innerHeight - 0.5) * 2;
});
function kumonosu_parallax() {
kumonosu_tx += (kumonosu_mx - kumonosu_tx) * 0.05;
kumonosu_ty += (kumonosu_my - kumonosu_ty) * 0.05;
document.querySelectorAll(".kumonosu-card").forEach((card) => {
const d = parseFloat(card.dataset.depth) || 8;
card.style.translate = `${kumonosu_tx * d}px ${kumonosu_ty * d * 0.5}px`;
});
requestAnimationFrame(kumonosu_parallax);
}
kumonosu_parallax();
// CARD HOVER 3D
document.querySelectorAll(".kumonosu-card").forEach((card) => {
card.addEventListener("mousemove", (e) => {
const r = card.getBoundingClientRect();
const px = (e.clientX - r.left) / r.width - 0.5;
const py = (e.clientY - r.top) / r.height - 0.5;
gsap.to(card, {
rotateX: -py * 20,
rotateY: px * 20,
scale: 1.1,
zIndex: 20,
duration: 0.4,
ease: "power2.out",
transformPerspective: 800,
overwrite: "auto"
});
});
card.addEventListener("mouseleave", () => {
gsap.to(card, {
rotateX: 0,
rotateY: 0,
scale: 1,
zIndex: card.style.zIndex || "",
duration: 0.8,
ease: "elastic.out(1, 0.6)",
overwrite: "auto"
});
});
});
// SCROLL TRIGGER
ScrollTrigger.create({
trigger: "body",
start: "top top",
end: "bottom bottom",
scrub: 1,
onUpdate: (self) => {
const p = self.progress;
const moves = [{
x: -350,
y: -150,
rot: -40
}, {
x: -280,
y: -50,
rot: -25
}, {
x: -180,
y: 50,
rot: -15
}, {
x: -80,
y: 120,
rot: -5
}, {
x: 80,
y: 120,
rot: 5
}, {
x: 180,
y: 50,
rot: 15
}, {
x: 280,
y: -50,
rot: 25
}, {
x: 350,
y: -150,
rot: 40
}];
document.querySelectorAll(".kumonosu-card").forEach((card, i) => {
const m = moves[i];
const rest = parseFloat(card.dataset.restRot) || 0;
gsap.set(card, {
x: m.x * p,
y: m.y * p,
rotation: rest + m.rot * p
});
});
}
});
https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js
https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js
このデモは、GSAPのアニメーションとCSSの3D transformを組み合わせて、複数のインタラクションが同時に機能するヒーローセクションを実装しています。
主な機能は以下のとおりです。
stagger: { from: "center" }で中央のカードから外側に広がる順序で出現します。yoyo: true, repeat: -1による無限ループです。data-depth属性の値が大きいカードほど大きく動き、奥行き感のある視差効果を生みます。requestAnimationFrameで補間しながら追従します。rotateX・rotateYが変化し、3Dチルト効果が発生します。離すとelastic.outイージングで元に戻ります。feTurbulenceフィルターをBase64化してCSSのbackground-imageとして適用し、mix-blend-mode: multiplyで全体にざらついた質感をオーバーレイしています。.kumonosu-card内の<img>のsrcを差し替えるだけで画像を変更できます。.kumonosu-card-N)を定義、JSのmoves配列にスクロール散開の座標を追加すれば枚数を変えられます。data-rot属性の値を変更すると、各カードの静止時の傾きが変わります。data-depth属性の値を変更すると、マウスパララックスの移動量が変わります。値が大きいほど大きく動きます。back.out(1.4)の値を変更すると、着地時のバウンス感を調整できます。y: +=10 + (i % 3) * 5の値を変更すると、浮遊の上下幅が変わります。moves配列内のx・y・rot値を変更すると、スクロール時の散らばり方を調整できます。--kumonosu-bgとbodyのbackgroundグラデーションを変更すると、全体の雰囲気が変わります。.kumonosu-grainのopacity(デフォルト0.06)を変更すると、ざらつきの強さが変わります。cdnjs.cloudflare.com)からGSAP 3.12.5とScrollTriggerを読み込んでいます。本番環境ではバージョン固定やローカルホスティングを推奨します。overwrite: "auto"でホバー時の競合を制御しています。独自にアニメーションを追加する場合は競合に注意してください。bodyにmin-height: 200vhが設定されており、ヒーロー自体はposition: fixedで固定表示されます。後続コンテンツを追加する場合はmin-heightの調整が必要です。mousemoveイベントに依存しているため、タッチのみの端末では機能しません。フロートとスクロール散開は正常に動作します。requestAnimationFrameでパララックスを毎フレーム更新し、さらにGSAPのフロートとScrollTriggerが並行するため、低スペック端末では処理が重くなる場合があります。will-change: transformでGPU合成を促進しています。zIndex: 20を設定して最前面に表示しますが、mouseleave後の戻り処理では元のzIndexが完全に復元されない場合があります。