PORTFOLIO & WEB DESIGN MEMORANDOM BLOG   
PORTFOLIO & WEB DESIGN MEMORANDOM BLOG   

【JavaScript】『GSAP × canvas』画像で連番アニメーションをスクロール制御する方法

こんにちは!今回は「スクロールに合わせてcanvas上で連番画像が切り替わるアニメーション」を紹介します!

See the Pen
Untitled
by ryotom (@ryotam-smoke)
on CodePen.

• canvas に画像を1枚ずつ描画しながら、スクロール位置に応じて画像を切り替えていく仕組みです。
• 背景が固定されて、ユーザーの操作で映像が進むような体験が作れます!

HTMLとCSS部分のコードのポイント

  1. <canvas id="image-sequence"></canvas>
  2. <div class="spacer"></div>

  1. canvas {
  2.   position: fixed;
  3.   top: 0;
  4.   left: 0;
  5.   width: 100vw;
  6.   height: 100vh;
  7.   display: block;
  8. }
  9. .spacer {
  10.   height: 5000px;
  11. }

• canvas は画面いっぱいに固定表示(position: fixed)。
• .spacer はスクロール領域を作るためのダミー(高さ5000px)。
• body はスクロール可能にするための背景とマージン調整。

JavaScriptとGSAPの処理

仮画像の読み込み

  1. const currentFrame = index =>
  2.   `https://placehold.co/800x600/222/fff?text=Frame+${index.toString().padStart(2, "0")}`;

placehold.co を使って「Frame 01」「Frame 02」…の画像URLを自動生成します!

画像の事前ロード

  1. for (let i = 0; i < frameCount; i++) {
  2.   const img = new Image();
  3.   img.src = currentFrame(i);
  4.   images.push(img);
  5. }

連番画像を全部読み込んで images[] に保存しておきます。

canvas に描画する処理

  1. function render() {
  2.   const img = images[Math.floor(imageSeq.frame)];
  3.   if (img && img.complete) {
  4.     context.clearRect(0, 0, canvas.width, canvas.height);
  5.     const scale = Math.min(
  6.       canvas.width / img.width,
  7.       canvas.height / img.height
  8.     );
  9.     const x = (canvas.width - img.width * scale) / 2;
  10.     const y = (canvas.height - img.height * scale) / 2;
  11.     context.drawImage(img, x, y, img.width * scale, img.height * scale);
  12.   }
  13. }

画像をcanvasの中央に等倍縮小して描画する処理です。
スクロールのたびに呼び出されるので毎フレーム描画!

ScrollTriggerで連動させる

  1. gsap.to(imageSeq, {
  2.   frame: frameCount - 1,
  3.   snap: "frame",
  4.   ease: "none",
  5.   scrollTrigger: {
  6.     scrub: 1,
  7.     pin: true,
  8.     trigger: canvas,
  9.     start: "top top",
  10.     end: "5000",
  11.   },
  12.   onUpdate: render,
  13. });

プロパティ 意味・効果
scrub: 1 スクロールにアニメーションを1秒ディレイ付きで滑らかに同期させる。
数字を指定すると、遅延の秒数になる。
pin: true スクロール中、要素(今回はcanvas)をその場に固定(ピン留め)して動かなくする。
trigger: canvas アニメーションの開始トリガーになる要素。ここでは<canvas>が対象。
start: “top top” トリガー要素の上端がビューポートの上端に来たときに開始。
end: “5000” スクロール距離の終点をピクセルで指定。ここでは5000pxスクロールで終了。
snap: “frame” 画像の切り替えを整数(frame単位)でスナップさせ、ガタつきを防止。
onUpdate: render アニメーションの進行ごとにrender()を実行して、canvasを描き直す。

本番画像で使うなら?

  1. const currentFrame = index =>
  2.   `./images/frame_${index.toString().padStart(4, "0")}.jpg`;

結果 → ./images/frame_0000.jpg, frame_0001.jpg, …, frame_0059.jpg

もし画像ファイル名が「frame1.jpg」「frame2.jpg」みたいにゼロ埋めなしであれば、.padStart() を外してもOKです!

まとめ

このテクニックを使えば、以下のような演出が簡単にできます:

• 動画風のスクロールエフェクト
• 製品の分解アニメーション
• グラフィックノベルやインタラクティブアート

MILMONA   MILMONA   MILMONA   MILMONA   MILMONA   
MILMONA   MILMONA   MILMONA   MILMONA   MILMONA