オープニング演出(イントロ→本編表示)

全画面のイントロ画面でブランド名とプログレスバーがカウントアップし、完了するとワイプ(clip-path)で本編コンテンツが現れる遷移演出。読み込みから本編へのつなぎを印象的に見せられます。「もう一度」ボタンでリプレイ可能。

#loader#intro#opening#transition

ライブデモ

使用例(お題: アイドルグループ 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で提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。