オープニング演出(イントロ→本編表示)
全画面のイントロ画面でブランド名とプログレスバーがカウントアップし、完了するとワイプ(clip-path)で本編コンテンツが現れる遷移演出。読み込みから本編へのつなぎを印象的に見せられます。「もう一度」ボタンでリプレイ可能。
ライブデモ
使用例(お題: アイドルグループ Sakura)
この技法を「アイドルグループ Sakura」というテーマのダミーサイトで実際に使った例です。
HTML
<!-- Sakura:オープニング演出。イントロ(プログレス)→ワイプで公式サイト本編へ -->
<div class="sk-stage" id="skStage">
<!-- 本編(背面に常駐し、ワイプで露出) -->
<main class="sk-main">
<header class="sk-bar">
<span class="sk-logo">🌸 Sakura</span>
<nav class="sk-nav"><span>NEWS</span><span>LIVE</span><span>MUSIC</span></nav>
</header>
<div class="sk-hero" style="--img:url('https://picsum.photos/700/500?random=63')">
<p class="sk-eyebrow">OFFICIAL WEBSITE</p>
<h1 class="sk-title">満開の、<br>その先へ。</h1>
<p class="sk-lead">7人が紡ぐ春の物語。新曲・ライブ情報を更新中。</p>
<button class="sk-replay" id="skReplay" type="button">もう一度</button>
</div>
</main>
<!-- イントロ(オーバーレイ)。完了でワイプして消える -->
<div class="sk-intro" id="skIntro">
<span class="sk-petal sk-petal--1">🌸</span>
<span class="sk-petal sk-petal--2">🌸</span>
<span class="sk-petal sk-petal--3">🌸</span>
<div class="sk-brand" id="skBrand">Sakura</div>
<div class="sk-track"><span class="sk-fill" id="skFill"></span></div>
<div class="sk-pct" id="skPct">0%</div>
</div>
</div>
CSS
/* Sakura:オープニング演出(イントロ → ワイプで本編) */
:root {
--pink: #ffd1e0;
--pink2: #ff8fb3;
--ink: #4a4450;
}
* { box-sizing: border-box; }
body {
margin: 0;
height: 400px;
font-family: "Hiragino Kaku Gothic ProN", system-ui, sans-serif;
overflow: hidden;
}
.sk-stage { position: relative; height: 400px; }
/* 本編 */
.sk-main { position: absolute; inset: 0; background: #fff; color: var(--ink); }
.sk-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 20px;
border-bottom: 1px solid var(--pink);
}
.sk-logo { font-size: 15px; font-weight: 700; color: #7a3b53; }
.sk-nav { display: flex; gap: 16px; font-size: 11px; color: var(--pink2); font-weight: 700; }
.sk-hero {
position: relative;
height: 340px;
padding: 46px 28px;
color: #fff;
background:
linear-gradient(180deg, rgba(122,59,83,0.3), rgba(122,59,83,0.6)),
var(--img) center/cover no-repeat;
}
.sk-eyebrow { margin: 0; font-size: 10px; letter-spacing: 0.3em; color: var(--pink); }
.sk-title { margin: 12px 0; font-size: 32px; line-height: 1.4; font-weight: 800; text-shadow: 0 2px 10px rgba(0,0,0,0.3); }
.sk-lead { margin: 0 0 20px; font-size: 13px; line-height: 1.8; color: rgba(255,255,255,0.92); }
.sk-replay {
padding: 10px 22px;
border: 0;
border-radius: 999px;
background: #fff;
color: #7a3b53;
font-size: 13px;
font-weight: 700;
cursor: pointer;
box-shadow: 0 8px 20px rgba(0,0,0,0.2);
transition: transform 0.15s ease;
}
.sk-replay:hover { transform: translateY(-2px); }
/* イントロオーバーレイ */
.sk-intro {
position: absolute;
inset: 0;
z-index: 5;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16px;
background: linear-gradient(160deg, #fff 0%, var(--pink) 100%);
/* ワイプ:clip-path を上方向に閉じて本編を露出 */
clip-path: inset(0 0 0 0);
transition: clip-path 0.8s cubic-bezier(0.7, 0, 0.2, 1);
}
.sk-intro.is-done { clip-path: inset(0 0 100% 0); }
.sk-brand {
font-size: 34px;
font-weight: 800;
letter-spacing: 0.18em;
color: #7a3b53;
}
.sk-track {
width: 180px;
height: 5px;
border-radius: 999px;
background: rgba(122,59,83,0.15);
overflow: hidden;
}
.sk-fill {
display: block;
height: 100%;
width: 0%;
border-radius: 999px;
background: linear-gradient(90deg, var(--pink2), #ff5d8a);
transition: width 0.2s ease;
}
.sk-pct { font-size: 12px; color: #7a3b53; letter-spacing: 0.1em; }
/* 舞う花びら */
.sk-petal { position: absolute; font-size: 16px; opacity: 0.8; animation: sk-fall 4s linear infinite; }
.sk-petal--1 { left: 18%; top: -10%; animation-delay: 0s; }
.sk-petal--2 { left: 52%; top: -10%; animation-delay: 1.3s; }
.sk-petal--3 { left: 78%; top: -10%; animation-delay: 2.4s; }
@keyframes sk-fall {
to { transform: translateY(440px) rotate(220deg); opacity: 0.2; }
}
@media (prefers-reduced-motion: reduce) {
.sk-intro, .sk-fill, .sk-replay { transition: none; }
.sk-petal { animation: none; }
}
JavaScript
// Sakura オープニング:プログレスをカウントアップ → 完了でワイプ → 本編表示
const intro = document.getElementById('skIntro');
const fill = document.getElementById('skFill');
const pct = document.getElementById('skPct');
const replay = document.getElementById('skReplay');
let p = 0;
let timer = null;
function run() {
if (!intro || !fill || !pct) return;
p = 0;
intro.classList.remove('is-done');
fill.style.width = '0%';
pct.textContent = '0%';
clearInterval(timer);
timer = setInterval(() => {
// 不規則に進めてリアルなロード感
p = Math.min(p + Math.random() * 12 + 4, 100);
const v = Math.round(p);
fill.style.width = v + '%';
pct.textContent = v + '%';
if (p >= 100) {
clearInterval(timer);
// 完了:少し溜めてワイプ
setTimeout(() => intro.classList.add('is-done'), 350);
}
}, 220);
}
// 「もう一度」でリプレイ
replay?.addEventListener('click', run);
// 初回起動
run();
コード
HTML
<!-- オープニング演出: イントロ画面(プログレス) → 完了でワイプして本編を表示 -->
<div class="or-stage" id="orStage">
<!-- 本編コンテンツ(背面に常駐し、ワイプで露出する) -->
<main class="or-main">
<header class="or-bar">
<span class="or-logo">AURORA</span>
<nav class="or-nav"><span>Work</span><span>About</span><span>Contact</span></nav>
</header>
<div class="or-hero">
<p class="or-eyebrow">CREATIVE STUDIO</p>
<h1 class="or-title">光をデザインする。</h1>
<p class="or-lead">読み込みから本編への遷移を、一枚のワイプで滑らかに。</p>
<button class="or-replay" id="orReplay" type="button">もう一度</button>
</div>
</main>
<!-- イントロ(オーバーレイ)。完了するとワイプして消える -->
<div class="or-intro" id="orIntro">
<div class="or-brand" id="orBrand">AURORA</div>
<div class="or-track"><span class="or-fill" id="orFill"></span></div>
<div class="or-pct" id="orPct">0%</div>
</div>
</div>
CSS
:root {
--bg: #0b1020;
--panel: #111a30;
--txt: #eef3ff;
--muted: rgba(238, 243, 255, .55);
--a1: #f9a8d4;
--a2: #c084fc;
--a3: #60a5fa;
--intro: #0a0e1c;
--wipe: .9s; /* ワイプ所要時間 */
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
font-family: "Segoe UI", system-ui, sans-serif;
background: var(--bg);
color: var(--txt);
}
/* ステージ: 360px領域に収め、内部はすべて absolute/fixed で重ねる */
.or-stage {
position: relative;
width: 100%;
height: 360px;
overflow: hidden;
background: var(--bg);
}
/* ===== 本編コンテンツ ===== */
.or-main {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
background:
radial-gradient(620px 420px at 80% -10%, rgba(192, 132, 252, .25) 0%, transparent 60%),
radial-gradient(560px 420px at 10% 120%, rgba(96, 165, 250, .22) 0%, transparent 60%),
var(--panel);
}
.or-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 26px;
border-bottom: 1px solid rgba(255, 255, 255, .08);
}
.or-logo {
font-weight: 800;
letter-spacing: .22em;
font-size: 15px;
}
.or-nav {
display: flex;
gap: 20px;
font-size: 12px;
color: var(--muted);
}
.or-hero {
flex: 1;
display: grid;
align-content: center;
gap: 12px;
padding: 0 36px;
}
.or-eyebrow {
margin: 0;
font-size: 11px;
letter-spacing: .3em;
color: var(--a1);
}
.or-title {
margin: 0;
font-size: 38px;
line-height: 1.15;
font-weight: 800;
background: linear-gradient(90deg, var(--a1), var(--a2), var(--a3));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.or-lead {
margin: 0;
max-width: 30em;
font-size: 13px;
line-height: 1.7;
color: var(--muted);
}
.or-replay {
justify-self: start;
margin-top: 8px;
padding: 10px 20px;
border: 0;
border-radius: 999px;
font: inherit;
font-size: 13px;
font-weight: 700;
color: #0b1020;
cursor: pointer;
background: linear-gradient(90deg, var(--a1), var(--a3));
box-shadow: 0 8px 22px rgba(96, 165, 250, .35);
transition: transform .15s ease, box-shadow .15s ease;
}
.or-replay:hover { transform: translateY(-2px); box-shadow: 0 12px 28px rgba(192, 132, 252, .45); }
.or-replay:active { transform: translateY(0); }
/* ===== イントロ(オーバーレイ) ===== */
.or-intro {
position: absolute;
inset: 0;
z-index: 2;
display: grid;
place-content: center;
justify-items: center;
gap: 18px;
background:
radial-gradient(500px 360px at 50% 40%, #15204a 0%, transparent 70%),
var(--intro);
/* clip-path で下から上へワイプして消す。初期は全面表示 */
clip-path: inset(0 0 0 0);
transition: clip-path var(--wipe) cubic-bezier(.76, 0, .24, 1);
}
/* 完了状態: 上方向へワイプ(下辺を100%まで詰める) */
.or-intro.is-done { clip-path: inset(0 0 100% 0); }
.or-brand {
font-size: 40px;
font-weight: 800;
letter-spacing: .26em;
padding-left: .26em; /* letter-spacing分の右寄りを補正 */
background: linear-gradient(90deg, var(--a1), var(--a2), var(--a3));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
/* 立ち上がりのフェードアップ */
opacity: 0;
transform: translateY(10px);
animation: or-rise .7s ease forwards;
}
.or-track {
position: relative;
width: 240px;
height: 4px;
border-radius: 999px;
background: rgba(255, 255, 255, .12);
overflow: hidden;
}
.or-fill {
position: absolute;
inset: 0;
width: 0%; /* JSで更新 */
border-radius: 999px;
background: linear-gradient(90deg, var(--a1), var(--a3));
}
.or-pct {
font-size: 12px;
letter-spacing: .12em;
color: var(--muted);
font-variant-numeric: tabular-nums;
}
@keyframes or-rise {
to { opacity: 1; transform: translateY(0); }
}
/* 本編出現時のわずかなズーム演出 */
.or-stage.is-revealed .or-main { animation: or-settle .8s ease both; }
@keyframes or-settle {
from { transform: scale(1.04); opacity: .6; }
to { transform: scale(1); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
.or-intro { transition-duration: .01s; }
.or-brand { animation: none; opacity: 1; transform: none; }
.or-stage.is-revealed .or-main { animation: none; }
}
JavaScript
// オープニング演出: プログレスをカウントアップ → 完了でワイプして本編を表示
const stage = document.getElementById('orStage');
const intro = document.getElementById('orIntro');
const fill = document.getElementById('orFill');
const pct = document.getElementById('orPct');
const replay = document.getElementById('orReplay');
const DURATION = 2200; // プログレス所要(ミリ秒)
let rafId = null; // 進行中アニメのID
let startTime = null;
// イージング(終盤を緩める)
const easeOut = (t) => 1 - Math.pow(1 - t, 3);
// 1フレーム更新
function frame(now) {
if (startTime === null) startTime = now;
const t = Math.min(1, (now - startTime) / DURATION);
const p = Math.round(easeOut(t) * 100);
if (fill) fill.style.width = p + '%';
if (pct) pct.textContent = p + '%';
if (t < 1) {
rafId = requestAnimationFrame(frame);
} else {
rafId = null;
reveal(); // 完了したらワイプ
}
}
// イントロをワイプして本編を見せる
function reveal() {
intro?.classList.add('is-done');
stage?.classList.add('is-revealed');
}
// 最初から再生(リプレイ兼用)
function play() {
// 進行中のRAFを止めて状態をリセット
if (rafId !== null) { cancelAnimationFrame(rafId); rafId = null; }
startTime = null;
intro?.classList.remove('is-done');
stage?.classList.remove('is-revealed');
if (fill) fill.style.width = '0%';
if (pct) pct.textContent = '0%';
// ブランド名のフェードアップを再トリガ(reflowを挟む)
const brand = document.getElementById('orBrand');
if (brand) {
brand.style.animation = 'none';
void brand.offsetWidth; // 強制リフロー
brand.style.animation = '';
}
// 本編の出現演出が再び走るように、ワイプ完了後に開始
rafId = requestAnimationFrame(frame);
}
// 「もう一度」でリプレイ
replay?.addEventListener('click', play);
// 初回起動
play();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「オープニング演出(イントロ→本編表示)」の効果を追加してください。
# 追加してほしい効果
オープニング演出(イントロ→本編表示)(ローダー & スケルトン)
全画面のイントロ画面でブランド名とプログレスバーがカウントアップし、完了するとワイプ(clip-path)で本編コンテンツが現れる遷移演出。読み込みから本編へのつなぎを印象的に見せられます。「もう一度」ボタンでリプレイ可能。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- オープニング演出: イントロ画面(プログレス) → 完了でワイプして本編を表示 -->
<div class="or-stage" id="orStage">
<!-- 本編コンテンツ(背面に常駐し、ワイプで露出する) -->
<main class="or-main">
<header class="or-bar">
<span class="or-logo">AURORA</span>
<nav class="or-nav"><span>Work</span><span>About</span><span>Contact</span></nav>
</header>
<div class="or-hero">
<p class="or-eyebrow">CREATIVE STUDIO</p>
<h1 class="or-title">光をデザインする。</h1>
<p class="or-lead">読み込みから本編への遷移を、一枚のワイプで滑らかに。</p>
<button class="or-replay" id="orReplay" type="button">もう一度</button>
</div>
</main>
<!-- イントロ(オーバーレイ)。完了するとワイプして消える -->
<div class="or-intro" id="orIntro">
<div class="or-brand" id="orBrand">AURORA</div>
<div class="or-track"><span class="or-fill" id="orFill"></span></div>
<div class="or-pct" id="orPct">0%</div>
</div>
</div>
【CSS】
:root {
--bg: #0b1020;
--panel: #111a30;
--txt: #eef3ff;
--muted: rgba(238, 243, 255, .55);
--a1: #f9a8d4;
--a2: #c084fc;
--a3: #60a5fa;
--intro: #0a0e1c;
--wipe: .9s; /* ワイプ所要時間 */
}
* { box-sizing: border-box; }
body {
margin: 0;
min-height: 100vh;
display: grid;
place-items: center;
font-family: "Segoe UI", system-ui, sans-serif;
background: var(--bg);
color: var(--txt);
}
/* ステージ: 360px領域に収め、内部はすべて absolute/fixed で重ねる */
.or-stage {
position: relative;
width: 100%;
height: 360px;
overflow: hidden;
background: var(--bg);
}
/* ===== 本編コンテンツ ===== */
.or-main {
position: absolute;
inset: 0;
display: flex;
flex-direction: column;
background:
radial-gradient(620px 420px at 80% -10%, rgba(192, 132, 252, .25) 0%, transparent 60%),
radial-gradient(560px 420px at 10% 120%, rgba(96, 165, 250, .22) 0%, transparent 60%),
var(--panel);
}
.or-bar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 26px;
border-bottom: 1px solid rgba(255, 255, 255, .08);
}
.or-logo {
font-weight: 800;
letter-spacing: .22em;
font-size: 15px;
}
.or-nav {
display: flex;
gap: 20px;
font-size: 12px;
color: var(--muted);
}
.or-hero {
flex: 1;
display: grid;
align-content: center;
gap: 12px;
padding: 0 36px;
}
.or-eyebrow {
margin: 0;
font-size: 11px;
letter-spacing: .3em;
color: var(--a1);
}
.or-title {
margin: 0;
font-size: 38px;
line-height: 1.15;
font-weight: 800;
background: linear-gradient(90deg, var(--a1), var(--a2), var(--a3));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.or-lead {
margin: 0;
max-width: 30em;
font-size: 13px;
line-height: 1.7;
color: var(--muted);
}
.or-replay {
justify-self: start;
margin-top: 8px;
padding: 10px 20px;
border: 0;
border-radius: 999px;
font: inherit;
font-size: 13px;
font-weight: 700;
color: #0b1020;
cursor: pointer;
background: linear-gradient(90deg, var(--a1), var(--a3));
box-shadow: 0 8px 22px rgba(96, 165, 250, .35);
transition: transform .15s ease, box-shadow .15s ease;
}
.or-replay:hover { transform: translateY(-2px); box-shadow: 0 12px 28px rgba(192, 132, 252, .45); }
.or-replay:active { transform: translateY(0); }
/* ===== イントロ(オーバーレイ) ===== */
.or-intro {
position: absolute;
inset: 0;
z-index: 2;
display: grid;
place-content: center;
justify-items: center;
gap: 18px;
background:
radial-gradient(500px 360px at 50% 40%, #15204a 0%, transparent 70%),
var(--intro);
/* clip-path で下から上へワイプして消す。初期は全面表示 */
clip-path: inset(0 0 0 0);
transition: clip-path var(--wipe) cubic-bezier(.76, 0, .24, 1);
}
/* 完了状態: 上方向へワイプ(下辺を100%まで詰める) */
.or-intro.is-done { clip-path: inset(0 0 100% 0); }
.or-brand {
font-size: 40px;
font-weight: 800;
letter-spacing: .26em;
padding-left: .26em; /* letter-spacing分の右寄りを補正 */
background: linear-gradient(90deg, var(--a1), var(--a2), var(--a3));
-webkit-background-clip: text;
background-clip: text;
color: transparent;
/* 立ち上がりのフェードアップ */
opacity: 0;
transform: translateY(10px);
animation: or-rise .7s ease forwards;
}
.or-track {
position: relative;
width: 240px;
height: 4px;
border-radius: 999px;
background: rgba(255, 255, 255, .12);
overflow: hidden;
}
.or-fill {
position: absolute;
inset: 0;
width: 0%; /* JSで更新 */
border-radius: 999px;
background: linear-gradient(90deg, var(--a1), var(--a3));
}
.or-pct {
font-size: 12px;
letter-spacing: .12em;
color: var(--muted);
font-variant-numeric: tabular-nums;
}
@keyframes or-rise {
to { opacity: 1; transform: translateY(0); }
}
/* 本編出現時のわずかなズーム演出 */
.or-stage.is-revealed .or-main { animation: or-settle .8s ease both; }
@keyframes or-settle {
from { transform: scale(1.04); opacity: .6; }
to { transform: scale(1); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
.or-intro { transition-duration: .01s; }
.or-brand { animation: none; opacity: 1; transform: none; }
.or-stage.is-revealed .or-main { animation: none; }
}
【JavaScript】
// オープニング演出: プログレスをカウントアップ → 完了でワイプして本編を表示
const stage = document.getElementById('orStage');
const intro = document.getElementById('orIntro');
const fill = document.getElementById('orFill');
const pct = document.getElementById('orPct');
const replay = document.getElementById('orReplay');
const DURATION = 2200; // プログレス所要(ミリ秒)
let rafId = null; // 進行中アニメのID
let startTime = null;
// イージング(終盤を緩める)
const easeOut = (t) => 1 - Math.pow(1 - t, 3);
// 1フレーム更新
function frame(now) {
if (startTime === null) startTime = now;
const t = Math.min(1, (now - startTime) / DURATION);
const p = Math.round(easeOut(t) * 100);
if (fill) fill.style.width = p + '%';
if (pct) pct.textContent = p + '%';
if (t < 1) {
rafId = requestAnimationFrame(frame);
} else {
rafId = null;
reveal(); // 完了したらワイプ
}
}
// イントロをワイプして本編を見せる
function reveal() {
intro?.classList.add('is-done');
stage?.classList.add('is-revealed');
}
// 最初から再生(リプレイ兼用)
function play() {
// 進行中のRAFを止めて状態をリセット
if (rafId !== null) { cancelAnimationFrame(rafId); rafId = null; }
startTime = null;
intro?.classList.remove('is-done');
stage?.classList.remove('is-revealed');
if (fill) fill.style.width = '0%';
if (pct) pct.textContent = '0%';
// ブランド名のフェードアップを再トリガ(reflowを挟む)
const brand = document.getElementById('orBrand');
if (brand) {
brand.style.animation = 'none';
void brand.offsetWidth; // 強制リフロー
brand.style.animation = '';
}
// 本編の出現演出が再び走るように、ワイプ完了後に開始
rafId = requestAnimationFrame(frame);
}
// 「もう一度」でリプレイ
replay?.addEventListener('click', play);
// 初回起動
play();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。