🐧

Canvas2D + GSAPでぼよよーんを作った(ちょっとズルした)

2022/12/24に公開

はじめに

2022年最後の記事になります。
滑り込みセーフで書きます!!
今回はCanvas2Dで書いてますが、多分(というか絶対に)SVGで書いた方がいいと思います。
僕はSVG未勉強なのでCanvas2Dで書きました。
でも、勉強にはなったので結果オーライ!!

完成図 & コード全体像

codepenも貼りますが、ちょっと挙動とコードが違うので
完成図のコードは別で貼ります!!
https://codepen.io/con_ns_pgm/pen/NWBPzmG

JavaScript
import { CustomEase } from "./CustomEase.js";

const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

gsap.registerPlugin(CustomEase);

canvas.width = window.innerWidth;
canvas.height = 300;//これがカーソル位置を取得してビヨーンする範囲(正確には制御点の移動範囲になる)

const canvasW = canvas.width;
const canvasH = canvas.height;
const canvasPosY = canvas.getBoundingClientRect().top;
const canvasPosX = canvas.getBoundingClientRect().left;
const startX = 0;//線開始位置の座標
const endX = canvasW;//線終了位置の座標
const baseY = canvasH / 2;//線の基準Y座標
const controlBaseX = canvasW / 2;//制御点の基準X座標
const controlBaseY = canvasH / 2;//制御点の基準Y座標
//GSAPで制御できるように、制御点をオブジェクトで定義
let controlPoint = { controlX: canvasW / 2, controlY: canvasH / 2 };
let mouseX, mouseY;
let hovFlag = false;

//初期状態の線を描画
ctx.beginPath();
ctx.moveTo(startX,baseY);//曲線の始点座標
ctx.quadraticCurveTo(controlPoint.controlX,controlPoint.controlY,endX,baseY);
ctx.stroke();

//canvasの範囲内にカーソルが入った時
canvas.addEventListener('mousemove', function (e) {
  mouseX = e.clientX - canvasPosX;//canvas内のX座標を取得
  mouseY = e.clientY - canvasPosY;//canvas内のX座標を取得
  //カーソルがlineの上下10pxの位置にきたらhovFlagをtrueにする
  if (mouseY <= baseY + 10 && mouseY >= baseY - 10) {
    hovFlag = true;
  }

  //フラグがtrueの時(カーソルが棒の前後10pxの範囲を通ったら)制御点とカーソル位置の連動を開始
  if (hovFlag === true) {
    gsap.to(controlPoint, {
      controlX:mouseX,
      controlY: mouseY,
      duration: 0.2,
      ease:"Power0.easeNone",
      onUpdate: () => {
        //値が更新された後に実行される処理
        ctx.clearRect(0,0,canvasW,canvasH);
        ctx.beginPath();
        ctx.moveTo(startX, baseY);//曲線の始点座標
        //制御点の位置をカーソル座標に連動
	ctx.quadraticCurveTo(controlPoint.controlX,controlPoint.controlY,endX,baseY);
        ctx.stroke();
      }
    });
  }
});

//canvas範囲からカーソルが出たら
canvas.addEventListener('mouseout', function () {
  hovFlag = false;//ホバーフラグをfalseに戻す
  gsap.fromTo(controlPoint, {
    controlX: mouseX,
    controlY:mouseY,
  }, {
    controlX:controlBaseX,
    controlY: controlBaseY,
    ease: 
CustomEase.create("custom", "M0,0 C0,0 0.014,0.001 0.022,0.003 0.031,0.006 0.037,0.01 0.045,0.015 0.054,0.021 0.146,0.162 0.164,0.218 0.176,0.256 0.266,0.638 0.282,0.862 0.289,0.962 0.304,1.496 0.372,1.496 0.438,1.496 0.454,0.67 0.5,0.67 0.54,0.67 0.54,1.25 0.57,1.25 0.61,1.25 0.608,0.802 0.648,0.802 0.672,0.802 0.678,1.146 0.7,1.146 0.732,1.146 0.714,0.902 0.736,0.902 0.776,0.902 0.75,1.059 0.78,1.06 0.81,1.06 0.8,0.958 0.824,0.958 0.856,0.958 0.85,1.019 0.874,1.02 0.9,1.02 0.904,0.986 0.92,0.986 0.94,0.986 0.938,1.014 0.964,1.014 0.994,1.014 1,1 1,1 "),
    onUpdate: () => {
      ctx.clearRect(0,0,canvasW,canvasH);
      ctx.beginPath();
      ctx.moveTo(startX,baseY);//曲線の始点座標
      ctx.quadraticCurveTo(controlPoint.controlX,controlPoint.controlY,endX,baseY);
      ctx.stroke();
    }
  });
})

やっていること

  • canvas2Dで2次ベジェ曲線を描画。(初期状態では制御点をライン上中央に配置する)
  • hover検知用のfrag:hovFlagを定義
  • canvas内でのmousemoveイベント内で処理を開始
  • カーソルがラインの前後10pxの位置にきたらhoFlagをtrueにする。
    (ここでちょうどlineの位置にカーソル座標が来たら、と言う条件分岐をするのが理想だが
    カーソルの移動速度が速いとイコールにならない時があるため、ホバー検知ができなくなる。
    前後10pxずつ余裕を持たせるというズルをしました。お許し下さい)
  • hovFlagがtrueの時にGSAPを用いて、制御点の座標をカーソル座標に連動させる
    GSAPを使用している理由は、座標をトゥイーンさせたかったのと、より詳細にイージングを設定したいためです。
  • mouseoutでhovFragをfalseを戻す
  • GSAPのカスタムイージングを用いてぼよよん感を出す。

ぼよよん感を出す方法。詳しく。

欲しい動きとしては、カーソルが一定範囲を超えたらラインがぼよよーんと弾力があるように
ぼよんぼよんしながら元の位置に戻ることです(語彙力)
制御点の動きを考えます。

今回はカーソル座標をcanvas上における座標にしているので、
カーソルが範囲上側から外に出る場合、出る瞬間のカーソル座標は0です。(正確にはほぼ0)
そして、ラインの元の位置のy座標は1と考えます。
つまり、カーソルが範囲外に出た時ラインの制御点は 0 => 1 に向かって変化すれば良いことになります。
これをちょっとややこしい言い方をすれば正規化と言いますが、今はそんなことどうでもいいです。
こう言うときは、GSAPの出番です。デフォルトのイージングでは物足りないので
カスタムイージングを使いましょう。

これはイージングの曲線を表しています。
横軸が経過時間、縦軸が変化させたい数値です。今回は縦軸が制御点のy座標となります。
図の上の方にうっすら見える横線は「1」を表しています。つまり、今回はラインの元の位置のy座標です。
1を超えている理由ですが、ぼよよんの動きを想像して下さい。
バネのような弾力を表現するためには、一旦元のy座標を超えて反対側に行って戻ってきてまた超えて戻ってきて。。
そうして徐々に元の位置(1)に戻るような挙動になると思います。
それを表現するための、このイージングです。慣れないペンツールで書きました。

ちょっと長くなりましたが、これをGSAPのイージングにセットして
gsap.fromToで制御点の座標をカーソル座標から元の位置(ライン上)にトゥイーンさせています。
これで完成です。

ちょっと余談

ちなみにですが、GSAPのカスタムイージング機能は、普通にGSAPのCDNを読み込んだり
モジュールをインポートするだけでは使えません。
https://zenn.dev/73ch/articles/caf321051ecac211e7a5
こちらの記事に書いてあるように、別に読み込む必要があります。
そして、GSAPの無料会員登録をする必要がありました。
カスタムイージングは非常に便利な機能なので、これを機に登録しました。

記事では、npmで読み込んでいましたが
僕はアナログに
https://greensock.com/docs/v3/Installation
こちらからファイルを根こそぎダウンロードして
gsap-member => src => CustomEase.js
gsap-member => src => utils => paths.js
を読み込んで使用しました。
CustomEase内でpaths.jsを利用しているので、パスを通し直す必要があります。

終わりに

実装してみて、やっぱりGSAPは神だと言うのと
とっととSVG勉強しようと思います。
不明点やおかしいことがあればぜひコメントよろしくお願いします!!

Discussion