模写上級 #003 | ポートフォリオサンプルサイトーpagepiling


模写上級 #003は、スクロールするたびに1画面ずつページが積み重なるように切り替わるポートフォリオサイトを、pagePiling.jsとCSSアニメーションで再現する課題です。読み込み時に画面がシャッターのように開く演出と、表示した節の項目が順に現れる演出も含みます。

ライブラリを入れるだけでは、上級の課題になりません。ライブラリが用意する「節が切り替わった」というきっかけに、自分のCSSアニメーションをどうつなぐかを組み立てるのが、この課題の中心です。


差分チェックツールで効率UPお手本コードと自分のコードを比較して、違いを一目で確認できます。練習前にブックマークしておくと便利です。
Diff Checkerを開く →

このサイトで練習できること

演出ごとに、使う仕組みと、つまずきやすいポイントが違います。

演出使う仕組みつまずきやすいポイント
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.jsCSSのscroll-snap
必要なものjQueryとpagePiling.jsCSSだけ
切り替えの見え方次の節が上に積み重なるように重なる普通のスクロールで、節の頭に止まる
節が切り替わったときの処理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、初級から上級までの全課題は模写コーディング一覧にまとめています。