canvas 上で矢印を描く JavaScript

skitch のように HTML5 の canvas 上に矢印をひくためのライブラリです。簡単に使えるように組んでみました。

お絵かきツールなどを canvas を使って作る際のアイデアの一助になれば嬉しいです。スマホ対応も完了しました。

canvasArrow.js

canvasArrow.js デモ

上の灰色のキャンバス上でマウスを使って矢を書くことが出来ます。

スポンサーリンク

実施コード

以下のコードの読み込みを行ってください。

JavaScript

function canvasArrow(c, color, shape, shadowFlag) {
    var ctx = c.getContext("2d");
    var downX, downY, flag = 1;
    var canvasData=c.toDataURL();
    c.style.backgroundImage = "url("+canvasData+")";
    var arrowColor = color ? color : "#eb4654";
    
    c.addEventListener("touchstart", mDown, false);
    c.addEventListener("mousedown", mDown, false);
 
    var arrowStop = document.getElementsByClassName("stopArrow");
    for (var i = 0; i < arrowStop.length; i++) {
        arrowStop[i].addEventListener("click", function() {
            c.removeEventListener("touchstart", mDown, false);
            c.removeEventListener("mousedown", mDown, false);
        }, false)
    }
 
    function mLeave(e) {
        if (e.buttons === 1 || e.witch === 1 || e.type==="touchleave") {
            var img = new Image();
            img.src = canvasData;
            img.addEventListener("load", function() {
                ctx.drawImage(img, 0, 0);
            }, false);
        }
    }
 
    function mDown(e) {
        if (e.button === 0 || e.type === "touchstart") {
            if (e.type === "mousedown") {
                c.addEventListener("mouseup", mUp, false);
                c.addEventListener("mousemove", mMove, false);
                c.addEventListener("mouseleave", mLeave, false);
                c.removeEventListener("touchstart", mDown, false);
            } else {
                c.addEventListener("touchend", mUp, false);
                c.addEventListener("touchmove", mMove, false);
                c.addEventListener("touchleave", mLeave, false);
                c.removeEventListener("mousedown", mDown, false);
            }
            if (flag) {
                canvasData = c.toDataURL();
                c.style.backgroundImage = "url(" + canvasData + ")";
                flag = 0;
            }
            var rect = e.target.getBoundingClientRect();
            if (e.type === "mousedown") {
                downX = ~~(e.clientX - rect.left);
                downY = ~~(e.clientY - rect.top);
            } else {
                downX = ~~(e.changedTouches[0].clientX - rect.left);
                downY = ~~(e.changedTouches[0].clientY - rect.top);
            }
        }
    };
 
    function mUp(e) {
        if (e.button === 0 || e.type === "touchend") {
            var img = new Image();
            img.src = canvasData;
            img.addEventListener("load", function() {
                ctx.drawImage(img, 0, 0);
                var rect = e.target.getBoundingClientRect();
                if (e.type === "mouseup") {
                    var upX = ~~(e.clientX - rect.left);
                    var upY = ~~(e.clientY - rect.top);
                } else {
                    var upX = ~~(e.changedTouches[0].clientX - rect.left);
                    var upY = ~~(e.changedTouches[0].clientY - rect.top);
                }
                draw(downX, downY, upX, upY);
                canvasData = c.toDataURL();
                c.style.backgroundImage = "url(" + canvasData + ")";
            }, false);
            if (e.type === "mouseup") {
                c.removeEventListener("mouseup", mUp, false);
                c.removeEventListener("mousemove", mMove, false);
                c.removeEventListener("mouseleave", mLeave, false);
                c.addEventListener("touchstart", mDown, false);
            } else {
                c.removeEventListener("touchend", mUp, false);
                c.removeEventListener("touchmove", mMove, false);
                c.removeEventListener("touchleave", mLeave, false);
                c.addEventListener("mousedown", mDown, false);
            }
        }
    };
 
    function mMove(e) {
        if (e.buttons === 1 || e.witch === 1 || e.type === "touchmove") {
            e.preventDefault();
            ctx.clearRect(0, 0, c.width, c.height);
            var rect = e.target.getBoundingClientRect();
            if (e.type === "mousemove") {
                var moveX = ~~(e.clientX - rect.left);
                var moveY = ~~(e.clientY - rect.top);
            } else {
                var moveX = ~~(e.changedTouches[0].clientX - rect.left);
                var moveY = ~~(e.changedTouches[0].clientY - rect.top);
            }
            draw(downX, downY, moveX, moveY);
        };
    };
 
    function draw(dX, dY, uX, uY) {
        var theta = Math.atan2(uY - dY, -uX + dX);
        var distance = Math.sqrt((uY - dY) * (uY - dY) + (-uX + dX) * (-uX + dX));
        var l = Math.min(50, distance / 2);
        if (!shape) {
            ctx.beginPath();
            ctx.moveTo(dX, dY);
            var w = Math.min(13, distance / 10);
            if (distance < 60) {
                var tX = uX + 5 * Math.cos(theta);
                var tY = uY - 5 * Math.sin(theta);
            } else {
                var tX = uX + 30 * Math.cos(theta);
                var tY = uY - 30 * Math.sin(theta);
            }
            ctx.lineTo(tX, tY);
            ctx.lineCap = "round";
            ctx.lineWidth = w;
            ctx.strokeStyle = arrowColor;
            ctx.stroke();
        }
        ctx.beginPath();
        ctx.fillStyle = arrowColor;
        ctx.moveTo(uX, uY);
        ctx.lineTo(uX + l * Math.cos(theta + (Math.PI / 8)), uY - l * Math.sin(theta + (Math.PI / 8)));
        if (shape == 1) {
            ctx.lineTo(uX + (l * 0.8) * Math.cos(theta + (Math.PI / 12)), uY - (l * 0.8) * Math.sin(theta + (Math.PI / 12)));
            ctx.lineTo(dX, dY);
            ctx.lineTo(uX + (l * 0.8) * Math.cos(theta - (Math.PI / 12)), uY - (l * 0.8) * Math.sin(theta - (Math.PI / 12)));
        }
        ctx.lineTo(uX + l * Math.cos(theta - (Math.PI / 8)), uY - l * Math.sin(theta - (Math.PI / 8)));
        ctx.closePath();
        if (!shadowFlag) {
            ctx.shadowColor = "rgba(0, 0, 0, 0.5)";
            ctx.shadowBlur = 4;
            ctx.shadowOffsetX = -3;
            ctx.shadowOffsetY = 0;
        }
        ctx.fill();
    }
}

使い方

使い方は簡単です。

//canvas
<canvas id="myCanvas" width="700" height="400"></canvas>
 
<script>
//canvas タグを取得
var canvas = document.getElementById("myCanvas");
canvasArrow(canvas);
</script>

HTML 上の canvas タグを取得し、そのノードを関数の中に入れれば OK です。これだけで canvas 上に矢印を書くことが出来ます。

また、以下のオプションを付けることが出来ます。

色変更

第2引数は矢印の色の指定です。デフォルトは「"#eb4654"」です。

canvasArrow(canvas,"#0ff");

デモ

形変更

第3引数は矢印の形の指定です。形状0と形状1の2種類を用意しました。デフォルトは形状0です。形状1に変更したい場合は第3引数を「1」に指定します。どこかで見たような矢印の形状になります。

第2引数の色は「false」と指定することでデフォルトを維持します。
canvasArrow(canvas, false, 1);

デモ

影の有無

第4引数は矢印の影(ドロップシャドウ)の有無です。デフォルトは影有りです。影を消したい場合は「true」を指定します。

canvasArrow(canvas, false, 0, true);

デモ

画像上に矢印を描く

canvas 上に画像を埋め込めば画像上に矢印を書くことも出来ます。

var canvas = document.getElementById('myCanvas');
var ctx = canvas5.getContext('2d');
var img = new Image();
img.src = "画像URL";
img.onload = function() {
    ctx.drawImage(img, 0, 0);
    canvasArrow(canvas, false, 1);
};

デモ(画像上に矢印を引けます)

矢印のオン・オフボタン

矢印のオン・オフも出来ます。

オンにする場合は、イベントハンドラで canvasArrow() を付与します。オフはオフボタンの className を 「stopArrow」 に指定します。

<canvas id="myCanvas" width="700" height="400"></canvas>
<span id="startArrow">ON</span><span class="stopArrow">OFF</span>
 
<script>
//ID が startArrow のボタンを押すと矢印がオン
document.getElementById("startArrow").addEventListener("click",function(){
    canvasArrow(canvas);
},false);
</script>

デモ(「ON」を押すことで矢印が書けるようになります。)

ONOFF

既知のバグ

画像がパチパチと点滅することがある。

Firefox のみ起きるようです。Chrome は大丈夫でした。

まとめ

バグ等は一通りなくなったと思われます。これで、自作 skitch のような形も作れますね。

コード自体若干スパゲティ気味なので、もう少し見やすく使いやすい形に修正も考えています。頑張ります。

Top