高速スクロール・スピードライン
ゆっくり進んでいたページが突然ヒュンと高速移動し、流線とブラーの残像を残してバウンドしながら停止します。クリスマス特設サイトのような、移動そのものを楽しませるスクロール演出です。
ライブデモ
使用例(お題: アイドルグループ Sakura)
この技法を「アイドルグループ Sakura」というテーマのダミーサイトで実際に使った例です。
HTML
<!-- Sakura: 全国ツアー。スクロールで会場から会場へヒュンと高速移動 -->
<div class="stage" data-stage aria-label="ツアー会場を高速移動するスクロール演出">
<div class="track" data-track>
<div class="inner">
<section style="background:#2a0b3f">
<div class="num">TOKYO</div>
<svg class="icon" viewBox="0 0 100 100"><path d="M50 8 L61 38 L97 38 L68 60 L79 96 L50 74 L21 96 L32 60 L3 38 L39 38 Z" fill="#FFC2D6"/></svg>
</section>
<section style="background:#6a1f6e">
<div class="num">OSAKA</div>
<svg class="icon" viewBox="0 0 100 100"><path d="M50 86 C26 64 26 40 50 24 C74 40 74 64 50 86 Z" fill="#FF7DA8"/></svg>
</section>
<section style="background:#9B3F8E">
<div class="num">NAGOYA</div>
<svg class="icon" viewBox="0 0 100 100"><circle cx="38" cy="62" r="14" fill="#FFD9E0"/><rect x="50" y="20" width="6" height="44" fill="#FFD9E0"/><path d="M50 20 h24 v12 h-24 z" fill="#FFD9E0"/></svg>
</section>
<section style="background:#C2455A">
<div class="num">FUKUOKA</div>
<svg class="icon" viewBox="0 0 100 100"><path d="M50 14 C66 30 66 58 50 86 C34 58 34 30 50 14 Z" fill="#FFE3B0"/></svg>
</section>
<section style="background:#FF6FA8">
<div class="num">SAPPORO</div>
<svg class="icon" viewBox="0 0 100 100"><path d="M50 8 L61 38 L97 38 L68 60 L79 96 L50 74 L21 96 L32 60 L3 38 L39 38 Z" fill="#fff"/></svg>
</section>
</div>
</div>
<div class="lines" data-lines aria-hidden="true"></div>
</div>
CSS
* { box-sizing: border-box; }
html, body { margin: 0; width: 100%; min-height: 100%; background: #1a0626; }
.stage {
position: relative; width: 100%; height: 100vh;
min-height: 260px; max-height: 100%; overflow: hidden;
background: #1a0626; transition: opacity .3s ease;
}
.stage.fading { opacity: 0; }
.track { position: absolute; left: 0; top: 0; width: 100%; will-change: transform; }
.inner { transition: filter .15s ease, transform .15s ease; }
.stage.dashing .inner { filter: blur(4px); transform: scaleY(1.06); }
section { position: relative; height: 100vh; display: grid; place-items: center; overflow: hidden; }
.num {
font-family: "Arial Black", system-ui, sans-serif; font-weight: 900;
font-size: clamp(44px, 13vw, 120px); color: rgba(255,255,255,.95); line-height: 1; letter-spacing: .02em;
}
.icon { position: absolute; width: clamp(56px, 13vw, 110px); height: clamp(56px, 13vw, 110px); opacity: .85; }
.lines { position: fixed; inset: 0; pointer-events: none; opacity: 0; transition: opacity .15s ease; z-index: 3; }
.stage.dashing .lines { opacity: 1; }
.line { position: absolute; top: -160px; width: 2px; background: rgba(255,209,224,.6); animation: streak .3s linear infinite; }
@keyframes streak { from { transform: translateY(0); } to { transform: translateY(100vh); } }
JavaScript
// フェーズ制御は WAAPI、translate量は視点高さ基準でスケール(デモと同じロジック)。
(() => {
const stage = document.querySelector('[data-stage]');
const track = document.querySelector('[data-track]');
const lines = document.querySelector('[data-lines]');
if (!stage || !track || !lines) return;
for (let i = 0; i < 20; i++) {
const el = document.createElement('span');
el.className = 'line';
el.style.left = ((i * 137.5) % 100) + '%';
el.style.height = (80 + (i * 53) % 80) + 'px';
el.style.animationDelay = (-(i % 6) * 0.05) + 's';
lines.appendChild(el);
}
const wait = (ms) => new Promise(r => setTimeout(r, ms));
const anim = (kf, opts) => track.animate(kf, { fill: 'forwards', ...opts }).finished;
const ty = (v) => `translateY(${v}px)`;
async function loop() {
if (!track.animate) return;
while (true) {
const H = stage.clientHeight || 600;
const creep = -0.33 * H, dash = -3.2 * H, over = dash - 0.12 * H;
track.style.transform = ty(0);
stage.classList.remove('fading');
await anim([{ transform: ty(0) }, { transform: ty(creep) }], { duration: 1200, easing: 'linear' });
stage.classList.add('dashing');
await anim([{ transform: ty(creep) }, { transform: ty(dash) }], { duration: 900, easing: 'cubic-bezier(0.6,0,1,1)' });
stage.classList.remove('dashing');
await anim([{ transform: ty(dash) }, { transform: ty(over), offset: 0.5 }, { transform: ty(dash) }],
{ duration: 600, easing: 'cubic-bezier(0.34,1.56,0.64,1)' });
await wait(1000);
stage.classList.add('fading');
await wait(300);
}
}
loop();
})();
実装ガイド
使いどころ
イベントやツアー、キャンペーンの特設で、移動そのものを楽しませるスクロール演出に。緩→急の落差で疾走感を出します。
実装時の注意点
フェーズ制御はWAAPI(element.animate)で緩速前進→ダッシュ→着地スプリング→リセットを連結します。translate量は視点高さ基準でスケールさせ環境差に強くします。ダッシュ中だけコンテンツにblur+scaleY、固定配置の流線オーバーレイを表示。blurはコンテンツ側だけに当て、流線には当てません。
対応ブラウザ
Web Animations API・CSS filter/transformは全モダンブラウザで安定動作します。will-changeでダッシュのカクつきを抑え、prefers-reduced-motion時はダッシュを弱める配慮を。対応は実機で確認してください。
よくある失敗
blurを流線にまで当てると残像が濁ります。translateを固定pxにすると画面高さで見え方が破綻するため高さ基準でスケールします。複数タイマー併用はズレるのでフェーズはひとつの連結で扱います。
応用例
セクションを会場や章に、流線の色や本数を調整、実スクロール連動に、効果音と同期(音はサイト方針次第)などに発展できます。
コード
HTML
<!-- 高速スクロール・スピードライン:突然ヒュンと加速し流線とブラーを残して着地 -->
<div class="stage" data-stage aria-label="高速移動するスクロール演出">
<div class="track" data-track>
<div class="inner">
<section style="background:#0F1117">
<div class="num">01</div>
<svg class="icon" viewBox="0 0 100 100"><path d="M50 8 L61 38 L97 38 L68 60 L79 96 L50 74 L21 96 L32 60 L3 38 L39 38 Z" fill="#FFC83D"/></svg>
</section>
<section style="background:#2B4DA8">
<div class="num">02</div>
<svg class="icon" viewBox="0 0 100 100"><path d="M64 12 a40 40 0 1 0 24 70 a32 32 0 1 1 -24 -70 Z" fill="#D9F2FF"/></svg>
</section>
<section style="background:#5B3FA8">
<div class="num">03</div>
<svg class="icon" viewBox="0 0 100 100"><path d="M50 14 L88 46 H78 V86 H22 V46 H12 Z" fill="#FFD9E0"/><rect x="42" y="60" width="16" height="26" fill="#5B3FA8"/></svg>
</section>
<section style="background:#2E9E78">
<div class="num">04</div>
<svg class="icon" viewBox="0 0 100 100"><path d="M50 10 L74 50 H58 L78 84 H22 L42 50 H26 Z" fill="#E2FFE9"/><rect x="46" y="84" width="8" height="10" fill="#0F1117"/></svg>
</section>
<section style="background:#C0453F">
<div class="num">05</div>
<svg class="icon" viewBox="0 0 100 100"><path d="M50 8 L61 38 L97 38 L68 60 L79 96 L50 74 L21 96 L32 60 L3 38 L39 38 Z" fill="#FFF3C9"/></svg>
</section>
</div>
</div>
<div class="lines" data-lines aria-hidden="true"></div>
</div>
CSS
:root{
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--ease-inout: cubic-bezier(0.65, 0, 0.35, 1);
}
* { box-sizing: border-box; }
html, body { margin: 0; width: 100%; min-height: 100%; background: #0F1117; }
.stage {
position: relative;
width: 100%;
height: 100vh;
min-height: 260px;
max-height: 100%;
overflow: hidden;
background: #0F1117;
transition: opacity .3s ease;
}
.stage.fading { opacity: 0; }
.track { position: absolute; left: 0; top: 0; width: 100%; will-change: transform; }
/* ブラー/scaleYはWAAPIのtranslateと衝突しないよう内側要素に当てる */
.inner { transition: filter .15s ease, transform .15s ease; }
.stage.dashing .inner { filter: blur(4px); transform: scaleY(1.06); }
section {
position: relative;
height: 100vh;
display: grid;
place-items: center;
overflow: hidden;
}
.num {
font-family: "Arial Black", system-ui, sans-serif;
font-weight: 900;
font-size: clamp(80px, 22vw, 200px);
color: rgba(242,242,242,.92);
line-height: 1;
}
.icon {
position: absolute;
width: clamp(60px, 14vw, 120px);
height: clamp(60px, 14vw, 120px);
opacity: .9;
}
/* 流線オーバーレイ:ダッシュ中だけ表示。線は常時流れている */
.lines {
position: fixed; inset: 0;
pointer-events: none;
opacity: 0;
transition: opacity .15s ease;
z-index: 3;
}
.stage.dashing .lines { opacity: 1; }
.line {
position: absolute;
top: -160px;
width: 2px;
background: rgba(255,255,255,.5);
animation: streak .3s linear infinite;
}
@keyframes streak {
from { transform: translateY(0); }
to { transform: translateY(100vh); }
}
JavaScript
// フェーズ制御は WAAPI(track.animate)。translate量は視点高さ基準でスケールさせ環境差に強くする。
(() => {
const stage = document.querySelector('[data-stage]');
const track = document.querySelector('[data-track]');
const lines = document.querySelector('[data-lines]');
if (!stage || !track || !lines) return; // null安全
// 流線20本を生成(幅2px / 高さ80〜160px / left=(i*137.5)%100% / 位相ずらし)
for (let i = 0; i < 20; i++) {
const el = document.createElement('span');
el.className = 'line';
el.style.left = ((i * 137.5) % 100) + '%';
el.style.height = (80 + (i * 53) % 80) + 'px';
el.style.animationDelay = (-(i % 6) * 0.05) + 's';
lines.appendChild(el);
}
const wait = (ms) => new Promise(r => setTimeout(r, ms));
const anim = (kf, opts) => track.animate(kf, { fill: 'forwards', ...opts }).finished;
const ty = (v) => `translateY(${v}px)`;
async function loop() {
// ブラウザによっては finished が無いので保険
if (!track.animate) return;
while (true) {
const H = stage.clientHeight || 600;
const creep = -0.33 * H;
const dash = -3.2 * H;
const over = dash - 0.12 * H;
track.style.transform = ty(0);
stage.classList.remove('fading');
// 1. 微速前進
await anim([{ transform: ty(0) }, { transform: ty(creep) }], { duration: 1200, easing: 'linear' });
// 2. ダッシュ(ブラー+流線+scaleY)
stage.classList.add('dashing');
await anim([{ transform: ty(creep) }, { transform: ty(dash) }], { duration: 900, easing: 'cubic-bezier(0.6,0,1,1)' });
// 3. 着地:オーバーシュート→スプリングで戻る、効果解除
stage.classList.remove('dashing');
await anim(
[{ transform: ty(dash) }, { transform: ty(over), offset: 0.5 }, { transform: ty(dash) }],
{ duration: 600, easing: 'cubic-bezier(0.34,1.56,0.64,1)' });
// 4. 停止→フェードで先頭へ
await wait(1000);
stage.classList.add('fading');
await wait(300);
}
}
loop();
})();
🤖 AIエージェント用プロンプト
このままコピーしてAIに貼り付け「追加する場所」だけ書き換えればOK
あなたは熟練のフロントエンドエンジニアです。私のWebサイトに「高速スクロール・スピードライン」の効果を追加してください。
# 追加してほしい効果
高速スクロール・スピードライン(スクロール演出)
ゆっくり進んでいたページが突然ヒュンと高速移動し、流線とブラーの残像を残してバウンドしながら停止します。クリスマス特設サイトのような、移動そのものを楽しませるスクロール演出です。
# 追加する場所
👉【ここに対象箇所を記入:例「トップのヒーローセクション」「お問い合わせボタン」「記事カードの一覧」など】
# 参考実装(この見た目・挙動を再現してください)
【HTML】
<!-- 高速スクロール・スピードライン:突然ヒュンと加速し流線とブラーを残して着地 -->
<div class="stage" data-stage aria-label="高速移動するスクロール演出">
<div class="track" data-track>
<div class="inner">
<section style="background:#0F1117">
<div class="num">01</div>
<svg class="icon" viewBox="0 0 100 100"><path d="M50 8 L61 38 L97 38 L68 60 L79 96 L50 74 L21 96 L32 60 L3 38 L39 38 Z" fill="#FFC83D"/></svg>
</section>
<section style="background:#2B4DA8">
<div class="num">02</div>
<svg class="icon" viewBox="0 0 100 100"><path d="M64 12 a40 40 0 1 0 24 70 a32 32 0 1 1 -24 -70 Z" fill="#D9F2FF"/></svg>
</section>
<section style="background:#5B3FA8">
<div class="num">03</div>
<svg class="icon" viewBox="0 0 100 100"><path d="M50 14 L88 46 H78 V86 H22 V46 H12 Z" fill="#FFD9E0"/><rect x="42" y="60" width="16" height="26" fill="#5B3FA8"/></svg>
</section>
<section style="background:#2E9E78">
<div class="num">04</div>
<svg class="icon" viewBox="0 0 100 100"><path d="M50 10 L74 50 H58 L78 84 H22 L42 50 H26 Z" fill="#E2FFE9"/><rect x="46" y="84" width="8" height="10" fill="#0F1117"/></svg>
</section>
<section style="background:#C0453F">
<div class="num">05</div>
<svg class="icon" viewBox="0 0 100 100"><path d="M50 8 L61 38 L97 38 L68 60 L79 96 L50 74 L21 96 L32 60 L3 38 L39 38 Z" fill="#FFF3C9"/></svg>
</section>
</div>
</div>
<div class="lines" data-lines aria-hidden="true"></div>
</div>
【CSS】
:root{
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
--ease-spring: cubic-bezier(0.34, 1.56, 0.64, 1);
--ease-inout: cubic-bezier(0.65, 0, 0.35, 1);
}
* { box-sizing: border-box; }
html, body { margin: 0; width: 100%; min-height: 100%; background: #0F1117; }
.stage {
position: relative;
width: 100%;
height: 100vh;
min-height: 260px;
max-height: 100%;
overflow: hidden;
background: #0F1117;
transition: opacity .3s ease;
}
.stage.fading { opacity: 0; }
.track { position: absolute; left: 0; top: 0; width: 100%; will-change: transform; }
/* ブラー/scaleYはWAAPIのtranslateと衝突しないよう内側要素に当てる */
.inner { transition: filter .15s ease, transform .15s ease; }
.stage.dashing .inner { filter: blur(4px); transform: scaleY(1.06); }
section {
position: relative;
height: 100vh;
display: grid;
place-items: center;
overflow: hidden;
}
.num {
font-family: "Arial Black", system-ui, sans-serif;
font-weight: 900;
font-size: clamp(80px, 22vw, 200px);
color: rgba(242,242,242,.92);
line-height: 1;
}
.icon {
position: absolute;
width: clamp(60px, 14vw, 120px);
height: clamp(60px, 14vw, 120px);
opacity: .9;
}
/* 流線オーバーレイ:ダッシュ中だけ表示。線は常時流れている */
.lines {
position: fixed; inset: 0;
pointer-events: none;
opacity: 0;
transition: opacity .15s ease;
z-index: 3;
}
.stage.dashing .lines { opacity: 1; }
.line {
position: absolute;
top: -160px;
width: 2px;
background: rgba(255,255,255,.5);
animation: streak .3s linear infinite;
}
@keyframes streak {
from { transform: translateY(0); }
to { transform: translateY(100vh); }
}
【JavaScript】
// フェーズ制御は WAAPI(track.animate)。translate量は視点高さ基準でスケールさせ環境差に強くする。
(() => {
const stage = document.querySelector('[data-stage]');
const track = document.querySelector('[data-track]');
const lines = document.querySelector('[data-lines]');
if (!stage || !track || !lines) return; // null安全
// 流線20本を生成(幅2px / 高さ80〜160px / left=(i*137.5)%100% / 位相ずらし)
for (let i = 0; i < 20; i++) {
const el = document.createElement('span');
el.className = 'line';
el.style.left = ((i * 137.5) % 100) + '%';
el.style.height = (80 + (i * 53) % 80) + 'px';
el.style.animationDelay = (-(i % 6) * 0.05) + 's';
lines.appendChild(el);
}
const wait = (ms) => new Promise(r => setTimeout(r, ms));
const anim = (kf, opts) => track.animate(kf, { fill: 'forwards', ...opts }).finished;
const ty = (v) => `translateY(${v}px)`;
async function loop() {
// ブラウザによっては finished が無いので保険
if (!track.animate) return;
while (true) {
const H = stage.clientHeight || 600;
const creep = -0.33 * H;
const dash = -3.2 * H;
const over = dash - 0.12 * H;
track.style.transform = ty(0);
stage.classList.remove('fading');
// 1. 微速前進
await anim([{ transform: ty(0) }, { transform: ty(creep) }], { duration: 1200, easing: 'linear' });
// 2. ダッシュ(ブラー+流線+scaleY)
stage.classList.add('dashing');
await anim([{ transform: ty(creep) }, { transform: ty(dash) }], { duration: 900, easing: 'cubic-bezier(0.6,0,1,1)' });
// 3. 着地:オーバーシュート→スプリングで戻る、効果解除
stage.classList.remove('dashing');
await anim(
[{ transform: ty(dash) }, { transform: ty(over), offset: 0.5 }, { transform: ty(dash) }],
{ duration: 600, easing: 'cubic-bezier(0.34,1.56,0.64,1)' });
// 4. 停止→フェードで先頭へ
await wait(1000);
stage.classList.add('fading');
await wait(300);
}
}
loop();
})();
# 外部ライブラリ
なし(追加ライブラリ不要)
# 守ってほしいこと
- 既存のHTML構造・レイアウト・デザインを壊さないこと。必要に応じてクラス名・色・サイズを私のサイトに合わせて調整してよい。
- クラス名やidが既存と衝突しないよう、必要なら接頭辞で名前空間を分けること。
- レスポンシブ対応と prefers-reduced-motion への配慮を入れること。
- 私のサイトのフレームワーク(React / Vue / 素のHTML など)に合わせて実装すること。不明な場合は素のHTML/CSS/JSで提示し、組み込み手順も説明すること。
- 変更後の確認手順も簡潔に教えてください。