🔰

[GSAP, Canvas] スクロール時の画像モザイクアニメーション

2024/08/31に公開

この記事の概要

最近CanvasとWebGLの勉強を始めました。拙いコードですが、お手柔らかに。

まずは完成品をどうぞ。
GSAPのScrollTriggerとCanvasを使って、スクロール時のアニメーション「画像モザイク→モザイクなし」をやってみました。codePenに載せてます。

GSAPのScrollTrigger

Canvasの前にこちらをご覧頂きたい。

ScrollTriggerなんぞや?

アニメーション実装に特化した神ライブラリ"GSAP"、から提供されるプラグインの1つ。
ウィンドウスクロール時のイベントを簡単に設定できます。
自力で実装する場合はIntersection Observer APIとか使ってますが毎案件ほぼ確実に実装することになるのでGSAPで簡潔に実装できて助かってます。ありがとう、GSAP。

GSAP Installation

GSAPには有料機能もありますが、ScrollTriggerは無料で使えます。
自分はWebpackで開発環境組んでるのでnpmを使います。
npmのタブからチェックボックスをぽちぽちやると自動的に読み込み用のコード作ってくれます。
https://gsap.com/docs/v3/Installation?tab=npm&module=esm&method=private+registry&tier=free&club=false&require=false&trial=true

Canvasに描画した画像をモザイク化

canvasを使った画像のモザイク化はこちらの記事を参考にさせて頂きました。
https://qiita.com/akebi_mh/items/814aac25dad669293d2e

ポイントはCSS 「image-rendering: pixelated;」

ピクセルアート的なガビガビした画像にできます。これをcanvasに設定。
https://developer.mozilla.org/ja/docs/Web/CSS/image-rendering

スクロール時に画像サイズを徐々に変更する

スクロール時に少しずつcanvasのサイズを変更することでモザイクが徐々に取れていくように見せかけてます。
ScrollTriggerにonEnter関数があるのでこれを使います。あとは画像サイズを徐々に変化させる処理を走らせるだけ。

ScrollTrigger.create({
    trigger,
    start: 'top-=50 center',
    toggleClass: { targets: '.img-box', className: 'is-animation' },
    onEnter: () => {
      let blockSizeNum = myPixelatedImg.blockSize
      function galleryTick() {
        blockSizeNum -= 0.5
        myPixelatedImg.update(blockSizeNum)
        myReq = requestAnimationFrame(galleryTick)
        if (blockSizeNum <= 1) {
          // canvasのアニメーションが終わったら画像を表示
          trigger.classList.add('is-finished')
          cancelAnimationFrame(myReq)
        }
      }
      galleryTick()
    },
    once: true,
  })

モザイクのアニメーションが終わったら画像を表示

if (blockSizeNum <= 1) {
  // canvasのアニメーションが終わったら画像を表示
  trigger.classList.add('is-finished')
  cancelAnimationFrame(myReq)
}

canvasはcssのimage-rendering: pixelated;がついていてガビガビの画像なのでこれを非表示にし 、元画像にすり替えてます。
canvasのcssからimage-rendering: pixelated;を削除すればいいんでは?と思ったが、高画質な画像ではcanvas描画の画像の解像度が微妙だったので、
写真の見え感が元画像と違うなという悩みを持つぐらいならこれで良いかなと。

....いい方法ないですか?(小声)

CSSはここ。単純にopacityを0→1にしただけ。

.img-box.is-finished .target-img {
  opacity: 1;
}
.img-box.is-finished .target-canvas {
  opacity: 0;
}

最後にcancelAnimationFrameでループ処理を止めないと処理が走り続けることになるのでお忘れなく。
お疲れ様でした。

その他、ウィンドウリサイズの処理など

えー。
まだ終わりじゃないですよねぇ?

はい、そうです。。。

 // リサイズ
  window.onresize = () => {
    const blockSizeNum = myPixelatedImg.blockSize
    myPixelatedImg.update(blockSizeNum)
  }

canvasに描画した画像のレスポンシブ対応。
画像の幅が変わったら時にclassに登録しておいた関数を実行。
現在の画像幅でcanvasを描画しなおします。

実際に仕事で使う際はこれに加え、複数インスタンス生成して管理したり、
スライダーの1枚目のみに実装したりでとにかく考えること多かったのですが、今回は実装サンプルとして見せたいのでこんなもので。なんとかいけました。成長。

感想

やっと実用的なものができた。と感じた。
「CanvasとかWebGL勉強し始めたけど、実務でどうやって活かせばいいんだ?」とお考えの方は多いことでしょう。
いきなりthreee.js使ってバリバリ3Dアニメーションは敷居高すぎるので、今後も実用性高そうなサンプルが出来上がったら投稿していきたいと思います。よしなに🙂

Discussion