JavaScriptで
ぶらんこを漕ぐ
振り子の物理シミュレーションと
requestAnimationFrameを利用した
アニメーション効果で
ぶらんこを漕ぐ

image: JavaScriptでぶらんこを漕ぐ
Toplabo
Swing On A Swing With Js

Canvas 2D APIと物理シミュレーションでぶらんこをアニメーション化 

 | 2024/11/01

開発 経緯

発端はぶらんこに乗ってる女の子の写真から
  • ぶらんこに乗ってる女の子があまりにかわいいから

    という単純な動機からぶらんこの動きをjavascriptを使って再現してみました。
    最初はCSS3だけで開発してみましたが、違和感あったので、もっとできるだけリアルに再現するため、物理シミュレーションを使ってみることに。
    Canvas 2D APIを使うと物理シミュレーションが再現できるということでCanvas 2D APIで開発することに。
    後で調べてみると単純なHTMLとCSS、JSだけでも可能だとわかりましたが、Canvas 2D APIはあまりやったことがなかったので勉強のためCanvas 2D APIでの開発を選択。

サンプル デモ

まずはサンプルを
  • 何段階かの経緯を経ての最終デモをどうぞ

    最終的にはぶらんこを物理シミュレーションで再現し、ぶらんこも女の子の画像に差し替えました。
    ただ、物理シミュレーションで揺らすだけでは段々とふり幅が小さくなっていき、最後は止まってしまうため、ぶらんこを漕ぐ機能も実装。
    漕ぐ操作はマウスドラッグを選択、マウスを左右にドラッグするとぶらんこを漕ぐように設定しました。
    また、漕ぐ時に画像が何も変化しないのも味がないので、前方に漕ぐときは足を延ばして、後方へ漕ぐときは脚を後ろへ曲げる動きを実装。これもただ画像を差し替えるだけだとパタパタするだけで違和感あるので、綺麗なアニメーション効果を付ける工夫も入れました。
    実際のぶらんこと同様に前に触れているときに脚を後ろへ漕いだり、後ろ方向で前に漕ぐと振りが小さくなります。

解説

開発の範囲を区分けする
  • ぶらんこの開発を一度で全部は無理があるので、いくつかの範囲に区分けする

    ぶらんこに乗っている女の子のアニメーションというのが最終目標ですが、いきなりすべてを一度には難しいので、次の工程に区分けします。

    1.振り子の動きを物理シミュレーションで開発。
    2.ドラッグで漕ぐ機能を追加。
     前方(右)に漕ぐ(ドラッグする)と前方向に勢いが増し、後方(左)に漕ぐ(ドラッグする)と後方向に勢いが増す。
    3.振り子のボール部分を女の子の画像と差し替える。
    4.差し替えた画像が振り子の動きに合わせて揺れるような動きを実装する
    5. 漕ぐときに女の子の脚が漕ぐ動作をする。その時、脚の動きが自然に見えるようにアニメーション効果で補間する。
    以上のように5つの工程に分けました。
    次からそれぞれを解説。

    1.振り子の動きを物理シミュレーションで開発。

    この物理シミュレーションは今回はCanvas 2D APIで開発しますが、HTMLとCSS、Javascriptでも可能だそうです。
    機会をみて挑戦してみます。
    まずはCanvasの物理シミュレーションを。
    アニメーションはCSS3を使えないのでrequestAnimationFrameを使って描画していきます。
    振り子の物理シミュレーションで振り子:https://labo.studio-happyvalley.com/pendulum/sym/
    MDNによるCanvasのチュートリアル:https://developer.mozilla.org/ja/docs/Web/API/Canvas_API/Tutorial

    //Canvas 2D APIによる振り子の物理シミュレーション 
    const canvas = document.getElementById('pendulumCanvas');
    const ctx = canvas.getContext('2d');
    
    // 振り子の物理パラメータ
    const length = 180; // 振り子の長さ
    const gravity = 0.98; // 重力加速度
    let angle = Math.PI / 3; // 初期角度(45度)
    let angleVelocity = 0.0; // 角速度
    let angleAcceleration = 0.0; // 角加速度
    const damping = 0.999; // 減衰率 0.995
    
    // 振り子のアニメーション関数
    function updatePendulum() {
      // 振り子の力学方程式
      angleAcceleration = (-1 * gravity / length) * Math.sin(angle);
      angleVelocity += angleAcceleration;
      angleVelocity *= damping; // 減衰効果
      angle += angleVelocity;
    
      // 描画のクリア
      ctx.clearRect(0, 0, canvas.width, canvas.height);
    
      // 振り子の位置計算
      const originX = canvas.width / 2;
      const originY = 50;
      const pendulumX = originX + length * Math.sin(angle);
      const pendulumY = originY + length * Math.cos(angle);
    
      // 支点
      ctx.beginPath();
      ctx.arc(originX, originY, 5, 0, Math.PI * 2);
      ctx.fillStyle = 'black';
      ctx.fill();
    
      // 振り子の棒
      ctx.beginPath();
      ctx.moveTo(originX, originY);
      ctx.lineTo(pendulumX, pendulumY);
      ctx.strokeStyle = '#3498db';
      ctx.lineWidth = 2;
      ctx.stroke();
    
      // 振り子の重り
      ctx.beginPath();
      ctx.arc(pendulumX, pendulumY, 15, 0, Math.PI * 2);
      ctx.fillStyle = '#3498db';
      ctx.fill();
    }
    
    // アニメーションループ
    function animate() {
      updatePendulum();
      requestAnimationFrame(animate);
    }
    
    animate();
    2.漕ぐ機能を追加。

    振り子の物理シミュレーションは時間がたつと自然に止まってしまいます。
    そのため、マウスドラッグで振り角度を追加できるようにします。いわゆる“ぶらんこを漕ぐ”です。
    マウスドラッグを使用して、振り子の角速度(angleVelocity)にドラッグによる加速を追加します。
    マウスドラッグで振り子を漕ぐ:https://labo.studio-happyvalley.com/pendulum/push/

    3.振り子のボール部分を女の子の画像と差し替える。

    Canvas 2D APIでは画像を描画するにはdrawImage() メソッドを使用します。
    基本はdrawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight)となっていて、
    引数は、imageは事前に読みこんんだ画像オブジェクト、接頭語のsとdで始まる座標系と寸法系の値となっています。
    通常sの接頭語の値は省略して、d接頭語の値を使用します。
    それぞれ、x座標、y座標、画像の横幅、画像の高さとなっています。
    この工程でのポイントは画像と振り子棒の接続支点の調整です。今回は実測して設定しました。
    振り子のボールを画像と差し替えサンプル:https://labo.studio-happyvalley.com/pendulum/swing/
    MDNによるCanvasRenderingContext2D: drawImage() メソッド解説:https://developer.mozilla.org/ja/docs/Web/API/CanvasRenderingContext2D/drawImage

    4.差し替えた画像が振り子の動きに合わせて揺れるような動きを実装する

    振り子の角度に合わせて画像を回転させるには、canvas の save(), translate(), rotate(), drawImage() および restore() メソッドを組み合わせて画像を描画します。要は振り子の回転値を使って画像も回転させることになります。
    以下の様にrequestAnimationFrameに呼ばれるたびに画像の再描画を繰り返します。
    ここでのポイントは画像の位置決めの座標と、回転軸の支点の座標をそれぞれ設定することです。
    画像を振り子に合わせて揺らす:https://labo.studio-happyvalley.com/pendulum/swingR/


    //ctxはCanvasRenderingContext2D のインスタンス、いわゆるコンテキスト。
    ctx.translate(x座標, y座標);//回転時の支点、transform-originみたいな座標
    //angleは振り子の角度
    ctx.rotate(angle*-.5); // 振り子の角度に合わせて画像の回転角度を半分程度に調整
    ctx.drawImage(ぶらんこ画像オブジェクト, オフセットX, オフセットY); // オフセットを加えた位置に画像を描画
    5. 漕ぐときに女の子の脚が漕ぐ動作をする。その時、脚の動きが自然に見えるようにアニメーション効果で補間する。

    マウスドラッグで漕ぐ時、ほんとに漕いでるのか、また、どっち向きに漕いでるのかわかるように、前方向には足を延ばし後方には足を曲げるという動作を入れてみました。
    これまでのぶらんこの画像だけではなく脚先だけの画像を追加。代わりにぶらんこを漕ぐ女の子の画像から脚先を削除しました。
    この工程のポイントは上半身と下半身の接合支点です。
    やはり脚画像の位置決め座標と回転軸の二種類の座標指定が重要です。
    ピクセル測量ツールで実測しました。ほんとは計算でやりたかったのですが・・・。
    次はスマホ対応の開発ですね。
    ぶらんこを漕ぐデビルプリンセス:https://labo.studio-happyvalley.com/pendulum/swingCmp/

『JavaScriptでぶらんこを漕ぐ』関連のお薦め

このページで紹介しているプログラムやビジュアルなどご依頼いただければ実装を賜ります。
お問い合わせはこちら