奥行きレイヤースタック
同じ図形をZ軸方向に積層し、マウスで覗き込むように傾けて立体感を出すデモ。ロゴやキービジュアルの演出に応用できます。
ライブデモ
使用例(お題: SaaS FlowDesk)
この技法を「SaaS FlowDesk」というテーマのダミーサイトで実際に使った例です。
HTML
<!-- FlowDesk:ロゴマークをZ軸に積層したキービジュアル -->
<section class="fd-kv" aria-label="FlowDesk ブランドロゴ">
<div class="fd-kv__scene" id="fdScene">
<!-- 同じロゴ形状をZ方向に積層して立体感を出す -->
<div class="fd-stack" id="fdStack">
<span class="fd-mark" style="--z:0"></span>
<span class="fd-mark" style="--z:1"></span>
<span class="fd-mark" style="--z:2"></span>
<span class="fd-mark" style="--z:3"></span>
<span class="fd-mark fd-mark--top" style="--z:4"></span>
</div>
</div>
<div class="fd-kv__copy">
<span class="fd-kv__brand">▰ FlowDesk</span>
<h1 class="fd-kv__title">流れを、設計する。</h1>
<p class="fd-kv__lead">チームの業務フローを可視化し、自動化するワークOS。</p>
<span class="fd-kv__hint">マウスで覗き込むように傾きます</span>
</div>
</section>
CSS
/* FlowDesk:ロゴのZ軸レイヤースタック */
:root {
--navy: #0f1b34;
--blue: #4f7cff;
--white: #ffffff;
}
* { box-sizing: border-box; }
body {
margin: 0;
height: 400px;
display: grid;
grid-template-columns: 1fr 1fr;
align-items: center;
font-family: "Segoe UI", "Hiragino Kaku Gothic ProN", system-ui, sans-serif;
background:
radial-gradient(circle at 30% 30%, #1b2c52 0%, var(--navy) 65%);
color: var(--white);
overflow: hidden;
}
/* 左:3Dシーン。perspective が立体の肝 */
.fd-kv__scene {
height: 100%;
display: grid;
place-items: center;
perspective: 800px;
}
.fd-stack {
position: relative;
width: 150px;
height: 150px;
transform-style: preserve-3d; /* 子レイヤーを3D空間へ */
transform: rotateX(18deg) rotateY(-22deg);
transition: transform 0.2s ease-out;
}
/* 各ロゴ層:--z でZ位置と濃さを変える */
.fd-mark {
position: absolute;
inset: 0;
border-radius: 26px;
background: linear-gradient(135deg, #3a63e0, var(--blue));
border: 1px solid rgba(255,255,255,.25);
transform: translateZ(calc(var(--z) * 20px));
opacity: calc(0.35 + var(--z) * 0.14);
box-shadow: 0 10px 24px rgba(0,0,0,.25);
}
/* 中央に切り抜き風の窓を作りロゴ感を出す */
.fd-mark::before {
content: "";
position: absolute;
inset: 34%;
border-radius: 8px;
background: var(--navy);
}
.fd-mark--top {
background: linear-gradient(135deg, #6e93ff, #4f7cff);
box-shadow: 0 16px 34px rgba(79,124,255,.5);
}
.fd-mark--top::before { background: linear-gradient(135deg, #fff, #d7e1ff); }
/* 右:コピー */
.fd-kv__copy { padding: 0 30px 0 8px; }
.fd-kv__brand { font-size: 13px; font-weight: 800; letter-spacing: 0.1em; color: var(--blue); }
.fd-kv__title { margin: 12px 0 8px; font-size: 30px; font-weight: 900; letter-spacing: 0.02em; }
.fd-kv__lead { margin: 0 0 14px; font-size: 13px; line-height: 1.8; color: rgba(255,255,255,.72); }
.fd-kv__hint { font-size: 11px; color: rgba(255,255,255,.45); }
@media (prefers-reduced-motion: reduce) {
.fd-stack { transition: none; }
}
JavaScript
// FlowDesk レイヤースタック:マウス位置でスタック全体を覗き込むように傾ける
(() => {
const scene = document.getElementById("fdScene");
const stack = document.getElementById("fdStack");
if (!scene || !stack) return; // null安全
const reduce = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const baseX = 18, baseY = -22; // 初期傾き
const RANGE = 22; // 振れ幅(度)
scene.addEventListener("pointermove", (e) => {
if (reduce) return;
const r = scene.getBoundingClientRect();
const nx = (e.clientX - r.left) / r.width - 0.5; // -0.5〜0.5
const ny = (e.clientY - r.top) / r.height - 0.5;
const rx = baseX - ny * RANGE;
const ry = baseY + nx * RANGE;
stack.style.transform = `rotateX(${rx}deg) rotateY(${ry}deg)`;
});
// 離れたら初期姿勢へ戻す
scene.addEventListener("pointerleave", () => {
stack.style.transform = `rotateX(${baseX}deg) rotateY(${baseY}deg)`;
});
})();
コード
HTML
<div class="lstack-scene" aria-label="奥行きレイヤースタックのデモ">
<div class="lstack" id="lstack" tabindex="0" role="img" aria-label="重なった3Dレイヤー">
<!-- 同じ図形を奥行き方向に積層して立体感を出す -->
<span class="lstack__layer" style="--i:0"></span>
<span class="lstack__layer" style="--i:1"></span>
<span class="lstack__layer" style="--i:2"></span>
<span class="lstack__layer" style="--i:3"></span>
<span class="lstack__layer" style="--i:4"></span>
<span class="lstack__layer lstack__layer--top" style="--i:5">3D</span>
</div>
<p class="lstack-hint">マウスで覗き込む</p>
</div>
CSS
/* ===== 奥行きレイヤースタック ===== */
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 360px;
display: grid;
place-items: center;
font-family: "Segoe UI", system-ui, sans-serif;
background: radial-gradient(circle at 50% 40%, #18243f 0%, #070b16 60%, #03050b 100%);
overflow: hidden;
}
.lstack-scene { display: grid; place-items: center; gap: 16px; }
.lstack {
position: relative;
width: 180px;
height: 180px;
perspective: 900px;
transform-style: preserve-3d;
outline: none;
}
.lstack__layer {
position: absolute;
inset: 0;
margin: auto;
width: 150px;
height: 150px;
border-radius: 26px;
border: 1.5px solid hsl(calc(200 + var(--i) * 24) 90% 70% / .85);
background: hsl(calc(200 + var(--i) * 24) 80% 55% / .10);
box-shadow: 0 0 24px hsl(calc(200 + var(--i) * 24) 90% 60% / .25);
/* 各レイヤーを奥行き方向にずらす(--rx/--ry はJSが更新) */
transform:
rotateX(var(--rx, 14deg)) rotateY(var(--ry, -18deg))
translateZ(calc(var(--i) * 26px));
transition: transform .12s ease-out;
display: grid;
place-items: center;
}
.lstack__layer--top {
font-size: 56px;
font-weight: 900;
color: #fff;
letter-spacing: .04em;
background: hsl(320 80% 58% / .14);
border-color: hsl(320 90% 75% / .9);
text-shadow: 0 6px 18px rgba(0,0,0,.5);
}
.lstack-hint {
margin: 0;
font-size: 12px; letter-spacing: .2em;
color: rgba(180,200,255,.6);
}
@media (prefers-reduced-motion: reduce) {
.lstack__layer { transition: none; }
}
JavaScript
// 奥行きレイヤースタック: マウスで全レイヤーの傾きを連動更新
(() => {
const stack = document.getElementById('lstack');
if (!stack) return; // null安全
const layers = Array.from(stack.querySelectorAll('.lstack__layer'));
if (layers.length === 0) return;
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
let tRx = 14, tRy = -18; // 目標角度
let rx = 14, ry = -18; // 現在角度(補間用)
const onMove = (e) => {
const r = stack.getBoundingClientRect();
const px = (e.clientX - r.left) / r.width - 0.5;
const py = (e.clientY - r.top) / r.height - 0.5;
tRy = px * 50; // 左右で最大±25度
tRx = -py * 50 + 4; // 上下
};
const onLeave = () => { tRx = 14; tRy = -18; };
// 親要素全体でポインターを拾う(画面端でも反応)
document.addEventListener('pointermove', onMove);
stack.addEventListener('pointerleave', onLeave);
const apply = () => {
rx += (tRx - rx) * 0.1;
ry += (tRy - ry) * 0.1;
for (const el of layers) {
el.style.setProperty('--rx', rx.toFixed(2) + 'deg');
el.style.setProperty('--ry', ry.toFixed(2) + 'deg');
}
requestAnimationFrame(apply);
};
if (!reduce) requestAnimationFrame(apply);
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「奥行きレイヤースタック」の効果を追加してください。
# 追加してほしい効果
奥行きレイヤースタック(3D & パースペクティブ)
同じ図形をZ軸方向に積層し、マウスで覗き込むように傾けて立体感を出すデモ。ロゴやキービジュアルの演出に応用できます。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<div class="lstack-scene" aria-label="奥行きレイヤースタックのデモ">
<div class="lstack" id="lstack" tabindex="0" role="img" aria-label="重なった3Dレイヤー">
<!-- 同じ図形を奥行き方向に積層して立体感を出す -->
<span class="lstack__layer" style="--i:0"></span>
<span class="lstack__layer" style="--i:1"></span>
<span class="lstack__layer" style="--i:2"></span>
<span class="lstack__layer" style="--i:3"></span>
<span class="lstack__layer" style="--i:4"></span>
<span class="lstack__layer lstack__layer--top" style="--i:5">3D</span>
</div>
<p class="lstack-hint">マウスで覗き込む</p>
</div>
【CSS】
/* ===== 奥行きレイヤースタック ===== */
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 360px;
display: grid;
place-items: center;
font-family: "Segoe UI", system-ui, sans-serif;
background: radial-gradient(circle at 50% 40%, #18243f 0%, #070b16 60%, #03050b 100%);
overflow: hidden;
}
.lstack-scene { display: grid; place-items: center; gap: 16px; }
.lstack {
position: relative;
width: 180px;
height: 180px;
perspective: 900px;
transform-style: preserve-3d;
outline: none;
}
.lstack__layer {
position: absolute;
inset: 0;
margin: auto;
width: 150px;
height: 150px;
border-radius: 26px;
border: 1.5px solid hsl(calc(200 + var(--i) * 24) 90% 70% / .85);
background: hsl(calc(200 + var(--i) * 24) 80% 55% / .10);
box-shadow: 0 0 24px hsl(calc(200 + var(--i) * 24) 90% 60% / .25);
/* 各レイヤーを奥行き方向にずらす(--rx/--ry はJSが更新) */
transform:
rotateX(var(--rx, 14deg)) rotateY(var(--ry, -18deg))
translateZ(calc(var(--i) * 26px));
transition: transform .12s ease-out;
display: grid;
place-items: center;
}
.lstack__layer--top {
font-size: 56px;
font-weight: 900;
color: #fff;
letter-spacing: .04em;
background: hsl(320 80% 58% / .14);
border-color: hsl(320 90% 75% / .9);
text-shadow: 0 6px 18px rgba(0,0,0,.5);
}
.lstack-hint {
margin: 0;
font-size: 12px; letter-spacing: .2em;
color: rgba(180,200,255,.6);
}
@media (prefers-reduced-motion: reduce) {
.lstack__layer { transition: none; }
}
【JavaScript】
// 奥行きレイヤースタック: マウスで全レイヤーの傾きを連動更新
(() => {
const stack = document.getElementById('lstack');
if (!stack) return; // null安全
const layers = Array.from(stack.querySelectorAll('.lstack__layer'));
if (layers.length === 0) return;
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
let tRx = 14, tRy = -18; // 目標角度
let rx = 14, ry = -18; // 現在角度(補間用)
const onMove = (e) => {
const r = stack.getBoundingClientRect();
const px = (e.clientX - r.left) / r.width - 0.5;
const py = (e.clientY - r.top) / r.height - 0.5;
tRy = px * 50; // 左右で最大±25度
tRx = -py * 50 + 4; // 上下
};
const onLeave = () => { tRx = 14; tRy = -18; };
// 親要素全体でポインターを拾う(画面端でも反応)
document.addEventListener('pointermove', onMove);
stack.addEventListener('pointerleave', onLeave);
const apply = () => {
rx += (tRx - rx) * 0.1;
ry += (tRy - ry) * 0.1;
for (const el of layers) {
el.style.setProperty('--rx', rx.toFixed(2) + 'deg');
el.style.setProperty('--ry', ry.toFixed(2) + 'deg');
}
requestAnimationFrame(apply);
};
if (!reduce) requestAnimationFrame(apply);
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。