スクロール
【プロンプト有】CSSとJSで作る、スクロール連動の没入型メディアヒーローUI
2026/04/27
2026/4/18
ファーストビューでユーザーの興味を引きつけるには、ただの画像や動画を配置するだけでは不十分な場合があります。
このサンプルでは、スクロール操作に連動してメディアが拡大し、テキストや背景が段階的に変化する「没入型ヒーローUI」を実装しています。
動画を中心に据えたレイアウトに対して、CSSのtransformとJavaScriptによる進行度制御を組み合わせることで、自然で滑らかな体験を実現しています。LPやブランドサイトなど、世界観を重視する場面に適したUIです。
Code コード
<div class="hero-viewport">
<div class="background-image" id="bgImage"></div>
<div class="text-layer">
<div class="title-wrap">
<span class="title-part" id="titleLeft">没入型</span>
<span class="title-part" id="titleRight">ビジュアル体験</span>
</div>
<div class="info-text" id="dateText">デジタルショーケース</div>
<div class="scroll-hint" id="scrollHint">スクロールして展開</div>
<div class="final-content" id="finalContent">
<p>スクロール操作に応じてメディアが拡大し、視覚的に魅力的な体験を提供するUI</p>
</div>
</div>
<div class="media-container">
<div class="media-card" id="mediaCard">
<div class="video-overlay"></div>
<video id="heroVideo" autoplay loop muted playsinline poster="https://kumonosu.net/wp-content/uploads/2026/04/moorpheus-beach-418742.jpg">
<source src="https://kumonosu.net/wp-content/uploads/2026/04/344380.mp4" type="video/mp4">
</video>
</div>
</div>
</div>
:root {
--bg-color: #000;
--text-color: #fff;
--transition-speed: 0.1s;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-color: var(--bg-color);
color: var(--text-color);
font-family: "Helvetica Neue", Arial, "Hiragino Kaku Gothic ProN", "Hiragino Sans", Meiryo, sans-serif;
overflow-x: hidden;
height: 300vh;
}
.hero-viewport {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100vh;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
}
.background-image {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url('https://kumonosu.net/wp-content/uploads/2026/04/moorpheus-beach-418742.jpg');
background-size: cover;
background-position: center;
z-index: 1;
}
.media-container {
position: relative;
z-index: 10;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
pointer-events: none;
}
.media-card {
position: relative;
width: 70vw;
height: 40vw;
max-width: 900px;
max-height: 500px;
overflow: hidden;
border-radius: 24px;
box-shadow: 0 20px 50px rgba(0, 0, 0, 0.5);
transform: scale(0.6);
will-change: transform, width, height, border-radius;
}
video {
width: 100%;
height: 100%;
object-fit: cover;
}
.video-overlay {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.3);
z-index: 11;
}
.text-layer {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 20;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
pointer-events: none;
}
.title-wrap {
display: flex;
font-size: clamp(2rem, 8vw, 5rem);
font-weight: 900;
line-height: 1.2;
text-transform: uppercase;
white-space: nowrap;
}
.title-part {
display: inline-block;
transition: transform var(--transition-speed) linear;
}
.info-text {
margin-top: 20px;
font-size: 1.2rem;
letter-spacing: 0.2em;
transition: transform var(--transition-speed) linear, opacity 0.3s;
}
.scroll-hint {
position: absolute;
bottom: 40px;
font-size: 0.9rem;
opacity: 0.8;
animation: bounce 2s infinite;
}
@keyframes bounce {
0%, 20%, 50%, 80%, 100% {
transform: translateY(0);
}
40% {
transform: translateY(-10px);
}
60% {
transform: translateY(-5px);
}
}
.final-content {
position: absolute;
z-index: 30;
opacity: 0;
transform: translateY(30px);
text-align: center;
max-width: 600px;
padding: 20px;
pointer-events: none;
}
.final-content h2 {
font-size: 2.5rem;
margin-bottom: 20px;
}
.final-content p {
font-size: 1.1rem;
line-height: 1.8;
}
@media (max-width: 768px) {
.media-card {
width: 90vw;
height: 60vw;
}
.info-text {
font-size: 0.9rem;
}
}
const mediaCard = document.getElementById('mediaCard');
const bgImage = document.getElementById('bgImage');
const titleLeft = document.getElementById('titleLeft');
const titleRight = document.getElementById('titleRight');
const dateText = document.getElementById('dateText');
const scrollHint = document.getElementById('scrollHint');
const finalContent = document.getElementById('finalContent');
// スクロールイベントの監視
window.addEventListener('scroll', () => {
updateAnimation();
});
// モバイルのスワイプ操作はブラウザのデフォルトスクロールに委ねるが、
// 必要に応じて進行度を調整
function updateAnimation() {
// スクロール進行度の計算 (0 to 1)
const maxScroll = document.documentElement.scrollHeight - window.innerHeight;
const progress = Math.min(window.scrollY / maxScroll, 1);
// 1. ビデオカードの拡大と角丸の解除
// 進行度 0% (scale 0.6) -> 100% (scale 1.5くらいで画面を覆う)
const scaleVal = 0.6 + (progress * 1.2);
const radiusVal = 24 * (1 - progress);
mediaCard.style.transform = `scale(${scaleVal})`;
mediaCard.style.borderRadius = `${radiusVal}px`;
// 2. 背景画像のフェードアウト
bgImage.style.opacity = 1 - (progress * 2); // 早めに消す
// 3. タイトルの左右スライド分割
// 進行度に応じて左右に150%ずつ移動
const slideVal = progress * 150;
titleLeft.style.transform = `translateX(-${slideVal}%)`;
titleRight.style.transform = `translateX(${slideVal}%)`;
titleLeft.style.opacity = 1 - (progress * 2);
titleRight.style.opacity = 1 - (progress * 2);
// 4. 日付とヒントのフェードアウトとスライド
dateText.style.opacity = 1 - (progress * 3);
dateText.style.transform = `translateY(${progress * -50}px)`;
scrollHint.style.opacity = 1 - (progress * 4);
// 5. 最終コンテンツの表示 (progressが後半になったら表示)
if (progress > 0.8) {
const contentProgress = (progress - 0.8) * 5; // 0 to 1
finalContent.style.opacity = contentProgress;
finalContent.style.transform = `translateY(${(1 - contentProgress) * 20}px)`;
finalContent.style.pointerEvents = "auto";
} else {
finalContent.style.opacity = 0;
finalContent.style.transform = `translateY(20px)`;
finalContent.style.pointerEvents = "none";
}
}
// 初期実行
updateAnimation();
// リサイズ時の再計算対応
window.addEventListener('resize', updateAnimation);
Explanation 詳しい説明
基本構造
このUIは固定されたビューポート内でアニメーションを完結させる構成になっています。.hero-viewport を position: fixed にすることで、スクロールしても表示領域は固定され、その代わりに内部要素が変化します。
構造は「背景画像」「メディアカード(動画)」「テキストレイヤー」の3層で構成されています。背景は最下層、動画は中央、テキストは最前面に配置され、z-indexで明確にレイヤー管理されています。
仕様
アニメーションはスクロール量を基準に進行度(progress)を算出し、その値をもとに各要素へスタイルを適用しています。
メディアカードは scale を変化させることで拡大し、同時に border-radius を減少させることで「カード → フルスクリーン」へと自然に遷移します。
背景画像は progress に応じて opacity を下げることでフェードアウトし、メインビジュアルへの集中を促します。
タイトルは左右に分割され、それぞれが逆方向へスライドしながらフェードアウトします。この動きによって中央への視線誘導が強化されます。
補助テキストやスクロールヒントも同様にフェードアウトし、最終的には新しいコンテンツが下から表示される構成になっています。
カスタム
アニメーションのスピード感は progress に掛けている係数を調整することで変更できます。例えば scale の増加量を小さくすれば穏やかな演出になり、大きくすればダイナミックな印象になります。
border-radius の初期値を変更すればカードの形状も調整できます。角丸を強くすれば柔らかい印象になり、0に近づけるとシャープなデザインになります。
テキストの動きは translateX や translateY の値を変更することで演出の方向性を変えられます。例えば上下方向に分離する演出にも応用可能です。
動画の代わりに画像やcanvasを使用することもできるため、用途に応じて柔軟に拡張できます。
プロンプトについて
本記事のコードはGoogle AI Studioでプロンプトから生成しています。
一度でうまくいかない場合でも、何度か試したりプロンプトを調整することで、よりきれいなUIを作る可能性があります。
注意点
position: fixed を使用しているため、通常のスクロールコンテンツと組み合わせる場合はセクションの切り替えタイミングを設計する必要があります。
スクロール量を直接アニメーションに使用しているため、極端に短いページでは意図した動きにならない可能性があります。body の高さは適切に確保してください。
動画は自動再生(autoplay)を使用しているため、ブラウザの仕様上 muted が必須になります。音声付きで再生したい場合はユーザー操作をトリガーにする必要があります。
パフォーマンス面では transform と opacity のみを使用しているため比較的軽量ですが、モバイル環境では動画の読み込みや再生負荷に注意が必要です。
Prompt プロンプト
目的:
スクロールまたはスワイプ操作に応じて、中央の動画が拡大し、没入感のある演出を行う。
UI仕様:
・初期状態では動画は小さく中央に表示される
・スクロールまたはスワイプで動画が徐々に拡大する
・最大まで拡大すると全画面表示になる
・拡大に応じて背景画像はフェードアウトする
・動画の上に暗いオーバーレイを重ねる
・タイトルは2行に分割して左右にスライドする
・日付テキストと補助テキストも左右にスライドする
・拡大完了後に下部コンテンツがフェードインする
・モバイルではスワイプ操作に対応する
・上方向スワイプで元の状態に戻る
レイアウト仕様:
・中央配置のレスポンシブレイアウト
・画面中央に動画を配置
・背景にフルスクリーン画像を配置
・動画は角丸+影付きカード風デザイン
・テキストは中央揃え
アニメーション仕様:
・スクロール量に応じてサイズ・位置・透明度を変化させる
・動画は滑らかなトランジションで拡大・縮小する
・テキストは左右にスライドする
・コンテンツはフェードインで表示する
メディア仕様:
・動画は自動再生・ループ・ミュート
・画像・動画ともに横長レイアウトで表示する
・アスペクト比は維持し、歪ませない(object-fit: cover または contain)
データ構造:
・mediaType(image または video)
・mediaSrc(メディアURL)
・posterSrc(動画サムネイル・任意)
・bgImageSrc(背景画像)
・title(タイトル)
・date(日付)
・scrollText(スクロール案内テキスト)
・content(説明文)
動作仕様:
・スクロール量に応じて進行度を管理する
・進行度が最大になるとコンテンツ表示
・スクロール位置は固定し、UI内で完結させる
・モバイルではタッチ操作で同様の動作を行う
レスポンシブ要件:
・画面幅に応じてサイズ調整
・モバイルでは拡大速度を調整
・タッチ操作に最適化
生成条件:
・純粋なHTML / CSS / JavaScriptのみ使用
・外部フレームワークは禁止
・CSS Transform(scale / translate / opacity)を使用
・滑らかなアニメーションを実装する
デモ用データ:
・タイトル:没入型ビジュアル体験
・日付:デジタルショーケース
・スクロール案内:スクロールして展開
・説明文:スクロール操作に応じてメディアが拡大し、視覚的に魅力的な体験を提供するUI
画像・動画:
・動画:https://kumonosu.net/wp-content/uploads/2026/04/344380.mp4
・画像:https://kumonosu.net/wp-content/uploads/2026/04/moorpheus-beach-418742.jpg