模写上級 #003は、スクロールするたびに1画面ずつページが積み重なるように切り替わるポートフォリオサイトを、pagePiling.jsとCSSアニメーションで再現する課題です。読み込み時に画面がシャッターのように開く演出と、表示した節の項目が順に現れる演出も含みます。
ライブラリを入れるだけでは、上級の課題になりません。ライブラリが用意する「節が切り替わった」というきっかけに、自分のCSSアニメーションをどうつなぐかを組み立てるのが、この課題の中心です。
このサイトで練習できること
演出ごとに、使う仕組みと、つまずきやすいポイントが違います。
| 演出 | 使う仕組み | つまずきやすいポイント |
|---|---|---|
| 1画面ずつ切り替わるスクロール | pagePiling.js(jQueryが必要) | 節の数とアンカー名をそろえる |
| 表示した節の項目を順に出す | afterLoad / onLeave と active クラス | クラスを付けるだけでは動かず、CSS側に動きの定義が要る |
| シャッターで幕を開ける | 擬似要素の @keyframes | 終わったあとに画面の操作をふさがないようにする |
| メニューから節へ移動 | $.fn.pagepiling.moveTo() | リンクの既定の動きを止める |
実装のポイント
デモのコードから、演出ごとの要点を抜き出して解説します。jQueryとpagePiling.js 1.5.6はCDNから読み込んでいます。
pagePiling.jsを初期化する
節にしたい要素に .section を付け、親要素に対して pagepiling() を呼びます。anchors は節ごとのURLの名前で、節の数と同じだけ並べます。afterLoad は節を表示し終えたとき、onLeave は節から離れるときに呼ばれるので、ここで表示中の節に active クラスを付け外しします。
$('#pagepiling').pagepiling({
direction: 'vertical',
anchors: ['page1', 'page2', 'page3', 'page4', 'page5', 'page6'],
scrollingSpeed: 700,
css3: true,
navigation: {
textColor: '#fff',
bulletsColor: '#fff',
position: 'right'
},
keyboardScrolling: true,
sectionSelector: '.section',
afterLoad: function (anchorLink, index) {
$('.section').eq(index - 1).addClass('active');
},
onLeave: function (index, nextIndex, direction) {
$('.section').eq(index - 1).removeClass('active');
}
});表示した節の項目を順に出す
項目は最初、透明で少し下にずらしておき、節に active が付いたときだけアニメーションを再生します。順番に出す時間差は、jQueryで項目ごとに animation-delay を0.1秒ずつずらして作ります。
$('.service-item').each(function (index) {
$(this).css('animation-delay', (index * 0.1) + 's');
});.service-item,
.work-item,
.stat,
.team-roles span {
opacity: 0;
translate: 0 20px;
}
.section.active .service-item,
.section.active .work-item,
.section.active .stat,
.section.active .team-roles span {
animation: itemFadeUp 0.6s ease-out forwards;
}
@keyframes itemFadeUp {
to {
opacity: 1;
translate: 0 0;
}
}ここで transform ではなく、個別のプロパティ translate を動かしているのがポイントです。animation-fill-mode: forwards で transform の最後の値を残すと、その値がホバー時の transform より優先され、ホバーで動かす演出が効かなくなります。動かすプロパティを分けておけば、両方を同時に使えます。
シャッターで幕を開ける
画面全体を覆う .shutter の擬似要素を、まず横に細い線として伸ばし、次に縦へ広げて画面を埋めます。最後に .shutter 自体を透明にし、z-index: -1 で裏に回して、下のページを操作できるようにします。次のコードは要点だけを抜き出したもので、線の色(グラデーション)などの指定は省いています。
.shutter {
position: fixed;
inset: 0;
z-index: 9999;
background-color: var(--color-bg);
animation: byeShutter 2.6s forwards;
}
.shutter::before {
content: "";
position: absolute;
top: 0;
bottom: 0;
left: 0;
margin: auto;
animation: shutterOpen1 2.6s forwards;
}
@keyframes shutterOpen1 {
0% { width: 0; height: 0.1px; }
50% { width: 100%; height: 0.1px; }
90% { width: 100%; height: 100%; }
}
@keyframes byeShutter {
70% { opacity: 1; }
100% { opacity: 0; z-index: -1; }
}メニューからの移動はmoveToで行う
ヘッダーのメニューは、リンクの既定の動き(ページ内へのジャンプ)を止めて、pagePiling.jsの moveTo() に節の名前を渡します。こうすると、スクロールで切り替えたときと同じ動きで目的の節へ移れます。
$('a[href^="#page"]').on('click', function (e) {
e.preventDefault();
const target = $(this).attr('href').replace('#', '');
$.fn.pagepiling.moveTo(target);
});ハマりどころ:このデモで実際に直したこと
このデモは、節に active を付けるところまで作ってあったのに、項目が順に現れる動きが実際には起きていませんでした。2026年11月に、次の2点を直しています。
| 症状 | 原因 | 直し方 |
|---|---|---|
| 節を切り替えても、項目が順に現れない | JavaScriptで active と animation-delay を付けていたが、CSSに動きの定義(@keyframes)がなかった | .section.active の中の項目にアニメーションを定義する |
| (直す途中で)アニメーション後にホバーの動きが効かない | forwards で止めた transform がホバーの transform を上書きする | 動かすプロパティを translate に分ける |
クラスを付けるJavaScriptと、クラスを受けるCSSは別々のファイルにあるため、片方だけ書いて満足しがちです。開発者ツールで節に active が付いたことと、項目に animation-name が当たっていることの両方を確かめてください。あわせて、動きを減らす設定の人には最初から表示するようにしています。
@media (prefers-reduced-motion: reduce) {
.service-item,
.work-item,
.stat,
.team-roles span {
opacity: 1;
translate: none;
}
.section.active .service-item,
.section.active .work-item,
.section.active .stat,
.section.active .team-roles span {
animation: none;
}
}pagePiling.jsを使うか、CSSのscroll-snapで作るか
新しく作るなら、まずCSSのscroll-snapで足りるかを考え、節が上に積み重なるような演出が欲しいときだけpagePiling.jsを選ぶのがおすすめです。pagePiling.jsはjQueryが必要で、npmで公開されている最新版の1.5.6は2019年1月のものです。
| 比べる点 | pagePiling.js | CSSのscroll-snap |
|---|---|---|
| 必要なもの | jQueryとpagePiling.js | CSSだけ |
| 切り替えの見え方 | 次の節が上に積み重なるように重なる | 普通のスクロールで、節の頭に止まる |
| 節が切り替わったときの処理 | afterLoad などで受け取れる | IntersectionObserver等で自分で作る |
| 向いている場面 | プレゼンのように1画面ずつ見せる演出 | 一般的なLPやポートフォリオ |
.container {
height: 100vh;
overflow-y: auto;
scroll-snap-type: y mandatory;
}
.container > section {
height: 100vh;
scroll-snap-align: start;
}scroll-snapは、スクロールする枠に scroll-snap-type、中の節に scroll-snap-align を付けるだけで動きます(MDN:scroll-snap-type)。横スクロールのカードで使う例はFigma模写 #4:旅行サービス系UIを再現!上級者向けレイアウトに挑戦の追加課題で紹介しています。
よくある質問(FAQ)
模写上級 #003について、よくある質問をまとめました。
Q. PagePiling.jsとは何ですか?
PagePiling.jsは、Webページを節ごとに画面いっぱいに表示し、スクロールすると次の節が上に積み重なるように切り替えるjQueryのライブラリです。似た機能を持つfullPage.jsと違い、節がスライドして重なっていく見え方が特徴で、ポートフォリオやプレゼン型のページに向いています。
Q. フルスクリーンの節で止まるスクロールをCSSだけで作れますか?
作れます。スクロールする枠に scroll-snap-type: y mandatory、中の節に scroll-snap-align: start を付けると、ライブラリなしで節の頭に止まるスクロールになります。ただし、節が上に重なっていく見え方はCSSだけでは作れません。
Q. 節を切り替えても項目のアニメーションが動きません。何を確かめればよいですか?
開発者ツールで、節に active クラスが付いているかと、項目に animation-name が当たっているかの2つを確かめます。クラスは付いているのに動かない場合は、CSS側に @keyframes の定義がないか、セレクタの書き方が違っていることがほとんどです。
Q. シャッターの演出が終わったあと、ボタンが押せなくなるのはなぜですか?
画面全体を覆う要素が、透明になっても前面に残っているためです。アニメーションの最後で z-index を下げて裏に回すか、pointer-events: none を指定して、下のページを操作できるようにします。
Q. ほかの上級の模写課題はありますか?
あります。GSAPとScrollTriggerでスクロール演出を組む課題は模写上級 #005 | ファッション雑誌風LP、初級から上級までの全課題は模写コーディング一覧にまとめています。





