LoginSignup
0
0

More than 3 years have passed since last update.

【入門者向け】Canvas入門講座#12 円をマウスドラッグで動かそう【JavaScript】

Last updated at Posted at 2020-12-01

問題12

中心が(150, 150)、半径50の円を塗りつぶしなさい。
塗りつぶす色はマゼンタ色(#ff00ff)であること。
円をドラッグで動かせるようにしなさい。
円内をダウンした時のみ円を動かせること。
円のドラッグ中にマウスカーソルがcanvasの外に出た場合、円をドラッグを終了すること。
なお、以下のHTMLを使うこと。

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題12</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(() => {    
    $('#my-canvas').mousedown(e => {        
    });

    $('#my-canvas').mousemove(e => {        
    });

    $('#my-canvas').mouseup(e => {        
    });

    $('#my-canvas').mouseout(e => {        
    });
});
</script>
</head>
<body>
<canvas id="my-canvas" width="500" height="300"></canvas>
</body>
</html>

解答

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>問題12</title>
<script src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
<script>
$(() => {

    const RADIUS = 50;  // 円の半径
    let posX = 150, // 円の中心のX座標
        posY = 150, // 円の中心のY座標
        dragFlag = false,   // 円をドラッグ中か
        deltaX = 0,
        deltaY = 0;

    drawCircle(posX, posY);

    $('#my-canvas').mousedown(e => {
        // canvasの左上隅を原点とする座標を求める
        const downX = e.offsetX,
            downY = e.offsetY;

        const ret = hitCircle(posX, posY, RADIUS, downX, downY);
        if(!ret) {// 円内をダウンしていないので処理終了
            dragFlag = false;
            return;
        }

        // 円内をダウンしたので、フラグをtrueにする
        dragFlag = true;

        // ダウン位置を始点とし、円の中心位置を終点とするベクトルを求める
        deltaX = posX - downX;
        deltaY = posY - downY;
    });

    $('#my-canvas').mousemove(e => {
        if(!dragFlag) {// 円がドラッグ中でない
            return;
        }
        // canvasの左上隅を原点とする座標を求める
        const downX = e.offsetX,
            downY = e.offsetY;

        // 円の中心位置を更新する
        posX = downX + deltaX;
        posY = downY + deltaY;

        // 円を描画する
        drawCircle(posX, posY);
    });

    $('#my-canvas').mouseup(e => {
        // マウスボタンが離されたので、ドラッグ中フラグをfalseにする
        dragFlag = false;
    });

    $('#my-canvas').mouseout(e => {
        // マウスカーソルがcanvasの外に出たので、ドラッグ中フラグをfalseにする
        dragFlag = false;
    });

    // 円内をダウンしたか調べる
    function hitCircle(posX, posY, radius, downX, downY) {
        // (posX, posY) と (downX, downY) の距離を求める
        const dist = Math.sqrt((posX - downX) * (posX - downX) + (posY - downY) * (posY - downY));
        if(dist <= radius) {// 円内
            return true;
        } else {// 円外
            return false;
        }
    }

    function drawCircle(posX, posY) {
        // コンテキストを取得
        const ctx = $('#my-canvas')[0].getContext('2d');

        // 現在の描画状態を保存する
        ctx.save();

        // canvasをクリア
        ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);

        // 塗りつぶしの色をマゼンタ色にする
        ctx.fillStyle = '#ff00ff';        

        ctx.setTransform(1, 0, 0, 1, posX, posY);

        ctx.beginPath();        // 現在のパスをリセットする
        ctx.arc(0, 0, RADIUS, 0, Math.PI*2, true); // 円を描画する
        ctx.closePath();        // パスを閉じる
        ctx.fill();           // 現在のパスを塗りつぶす

        // 描画状態を保存した時点のものに戻す
        ctx.restore();
    }
});
</script>
</head>
<body>
<canvas id="my-canvas" width="500" height="300"></canvas>
</body>
</html>

円がドラッグで動きました。
image.png

解説

みなさん、プログラムは少し慣れてきたと思うので
今回はアルゴリズムベース(どういう風に実現するか)で説明していきます。

まずは、マウスダウン時に円の中か外であるか判定する必要があります。
単純の距離を計算して、それが50以内かどうかで判定しましょう。(hitCircleメソッドに該当)
$(x_1,y_1)$と$(x_2,y_2)$の距離は$\sqrt{(x_1-x_2)^2+(y_1-y_2)^2}$で計算できます。

次に、円の中をダウンした場合、ドラッグ中であるフラグを立てて、
ダウン位置から円の中心位置までのベクトルを求めます。
これは次に発生するであろうmosuemove(マウスを動かしたとき)時に円の座標を更新するためです。
下図を見てください。
左円がmousedown時、真ん中と右がmousemove時です。
mousemove時に取得できる座標は水色のxですが、更新するのは緑色の丸の円の中心座標ですので
その差分を足してあげる必要があります。
image.png

最後に円を描画します。
これは問題10,問題11とほぼ同じです。

これで終わりといいたいところですが
mouseup(マウスボタンを離した時)に円のドラッグを終了させたいので
ドラッグ中のフラグをfalseにする必要があります。

また、mouseout(canvasの外にマウスカーソル出た)時も
ドラッグ中のフラグをfalseにします。

補足

解答では、mousemoveで円を描画していますが
mousemove時に描画するのではなく、
mousemove時は円の位置情報だけ更新しておいて、requesAnimationFrameで描画すると、
パフォーマンスが良くなります。(問題11参照)
知っていて損はないテクニックです。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0