tkhrsskの日記

技術ネタなど

HTML5 canvasの直線にマウスが触れたかどうかを判定するjavascript

週末プログラミング。 canvas内で描画した線にマウスが触れたかどうかを判定するコード。 円や長方形に触れたかどうかのjavascriptサンプルはstackoverflowでそれなりにヒットするけど、直線は見つからなかったので書いてみた。

デモページ(触れたかどうかはコンソールに出力)
コード
<!DOCTYPE html>
<html>
<head>
    <meta name="viewport" content="width=device-width,initial-scale=1">
</head>
<body>
    <h1>Canvas</h1>
    <div class="wrapper">
        <canvas id="drawArea" width="300" height="300" style="background-color:#ecc;">Canvas not supported</canvas>
    </div>
    <p>直線にヒットしたかを判定する javascript サンプルプログラム</p>
    <p>判定方法は下記URLの計算式を参考にしました。</p>
    <a href="http://www.iot-kyoto.com/satoh/2016/01/16/hittest-001/">http://www.iot-kyoto.com/satoh/2016/01/16/hittest-001/</a>
</body>
<script>
    var lines = [
        { x1: 10, y1: 20, x2: 120, y2: 180, width: 2, style: "#c00"},
        { x1: 110, y1: 230, x2: 220, y2: 180, width: 6, style: "#000" },
        { x1: 210, y1: 5, x2: 170, y2: 10, width: 3, style: "#080" },
        { x1: 20, y1: 205, x2: 280, y2: 10, width: 10, style: "#00c" }
    ]
    function setup() {
        for (var i = 0; i < lines.length; i++) {
            line = lines[i];
            var angle = Math.atan2(line.y2 - line.y1, line.x2 - line.x1);
            line.sn = Math.sin(angle);
            line.cs = Math.cos(angle);
        }
    }
    function drawCanvas() {
        setup();
        var canvas = document.getElementById('drawArea');
        var context = canvas.getContext('2d');
        for (var i = 0; i < lines.length; i++) {
            line = lines[i];
            drawLine(context, line.x1, line.y1, line.x2, line.y2, line.width, line.style);
        }
        canvas.onmousemove = function (e) {
            var r = canvas.getBoundingClientRect();
            var x = e.clientX - r.left;
            var y = e.clientY - r.top;
            hover = false;
            for (var i = 0; i < lines.length; i++) {
                line = lines[i];
                hover = isHit(x, y, line.x1, line.y1, line.x2, line.y2, line.width, line.sn, line.cs);
                if (hover) {
                    console.log("Hit:" + i + "番目の線にヒットしました", x, y);
                    break;
                }
            }
        }
    }
    function isHit(px, py, x1, y1, x2, y2, h, sn, cs) {
        // Ref: http://www.iot-kyoto.com/satoh/2016/01/16/hittest-001/
        var ret = false;
        var xm1 = x1 - px;
        var ym1 = y1 - py;
        var xm2 = x2 - px;
        var ym2 = y2 - py;
        var xr1 = xm1 * cs + ym1 * sn;
        var yr1 = -xm1 * sn + ym1 * cs;
        var xr2 = xm2 * cs + ym2 * sn;
        var yr2 = -xm2 * sn + ym2 * cs;
        var sx = xr1;
        var sy = yr1 - h/2;
        var dx = xr2 - xr1;
        var dy = h;
        if (sx > 0 || sy > 0 || sx + dx < 0 || sy + dy <= 0) {
            ret = false;
        } else {
            ret = true;
        }
        return ret;
    }
    function drawLine(context, ax, ay, bx, by, w, s) {
        context.lineWidth = w;
        context.strokeStyle = s;
        context.beginPath();
        context.moveTo(ax, ay);
        context.lineTo(bx, by);
        context.stroke();
    }
    window.onload = () => {
        drawCanvas();
    };
</script>

</html>

2020/9/22 幅の判定が誤っていたため修正。