Javascript画布箭头 - 箭头未指向行尾

时间:2016-12-16 10:33:54

标签: javascript html5 canvas

我正在使用此代码在画布上绘制箭头:

var arrowCanvas = document.getElementById("arrowCanvas");
var ctx = arrowCanvas.getContext("2d");
drawArrow(ctx, 30, 10, 30, 100);

function drawArrow(ctx, fromx, fromy, tox, toy){
    //variables to be used when creating the arrow
    var headlen = 10;
    ctx.strokeStyle = "#cc0000";
    ctx.fillStyle = "#cc0000";
    ctx.lineWidth = 10;
    var angle = Math.atan2(toy-fromy,tox-fromx);

    //starting path of the arrow from the start square to the end square and drawing the stroke
    ctx.beginPath();
    ctx.moveTo(fromx, fromy);
    ctx.lineTo(tox, toy);
    ctx.stroke();

    //starting a new path from the head of the arrow to one of the sides of the point
    ctx.beginPath();
    ctx.moveTo(tox, toy);
    ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7));

    //path from the side point of the arrow, to the other side point
    ctx.lineTo(tox-headlen*Math.cos(angle+Math.PI/7),toy-headlen*Math.sin(angle+Math.PI/7));

    //path from the side point back to the tip of the arrow, and then again to the opposite side point
    ctx.lineTo(tox, toy);
    ctx.lineTo(tox-headlen*Math.cos(angle-Math.PI/7),toy-headlen*Math.sin(angle-Math.PI/7));

    //draws the paths created above
    ctx.stroke();
    ctx.fill();
}

(代码见于此答案:partly answered)。

我对此代码的问题在于,由于ctx.lineWidth,arrowHead指向指定的坐标(tox,toy)。在所提供的示例中,由于lineWidth = 10px,箭头指向画布y位置110。我希望它完全指向tox,玩具坐标,与箭头角度无关。

我的解决方法是将箭头的初始长度减少lineWidth的数量,但我在考虑它的箭头角度时失败了。

1 个答案:

答案 0 :(得分:4)

只需要一点点触发即可。 Bellow是一个解决3种类型的线连接的片段。斜角,斜角和圆角。

函数drawArrow从x,y,xx,yy绘制一条线,options设置箭头的各种尺寸。

该函数读取ctx.lineWidthctx.lineJoin值,以确定如何移动终点以确保它不会通过该点。

  • 对于“圆形”连接,移入的距离是线宽的一半
  • 对于“斜接”,距离是半线宽除以箭头尖角的一半
  • 对于“斜角”,距离是尖端的角度的一半乘以半线宽度

const PI = Math.PI;
const PI2 = PI * 2;
function drawArrow(ctx,x,y,xx,yy,options){
    function getDef(name,def){return options[name] !== undefined ? options[name] : def;}
    var w = getDef("width",5); // get settings
    var hs = getDef("headSize",15); // 
    var hw = getDef("headWidth",15); // 
    var dx = xx-x;
    var dy = yy-y;
    var dir = Math.atan2(dy,dx);
    var dist = Math.sqrt(dx*dx+dy*dy);
    var lineWidth = Number(ctx.lineWidth)
    var endMove = ctx.lineWidth/2; // assume round joins
    if(ctx.lineJoin === "miter"){
        endMove = Math.min(ctx.miterLimit,endMove / (hw / Math.sqrt(hs*hs+hw*hw)));
    }else if(ctx.lineJoin === "bevel"){
        endMove = endMove * Math.cos(Math.asin(hs / Math.sqrt(hs*hs+hw*hw)));
    }
    // move canvas coordinates so that the arrow starts at 0,0, 
    ctx.setTransform(1,0,0,1,x,y); 
    ctx.rotate(dir); // and is aligned to x
    dist -= endMove; // shorten for line width
    ctx.beginPath();
    ctx.moveTo(0,-w);
    ctx.lineTo(dist - hs,-w);
    ctx.lineTo(dist - hs,-hw);
    ctx.lineTo(dist ,0);
    ctx.lineTo(dist - hs,hw);
    ctx.lineTo(dist - hs,w);
    ctx.lineTo(0,w);
    ctx.stroke();
    ctx.fill();
}

var arrows = [
    {width : 5, headWidth : 10, headSize : 20, lineWidth : 5,line : "red", fill : "blue",join : "bevel", limit : 100},
    {width : 10, headWidth : 20, headSize : 20, lineWidth : 5,line : "Orange", fill : "blue",join : "miter", limit : 5},
    {width : 10, headWidth : 20, headSize : 20, lineWidth : 5,line : "Green", fill : "blue",join : "round", limit : 0},
]
var tempArrow = {width : 10, headWidth : 20, headSize : 20};
const numArrows = 3;
const mouseClear = 30;  


// main update function
function display(){
    ctx.setTransform(1,0,0,1,0,0); // reset transform
    ctx.globalAlpha = 1;           // reset alpha
    ctx.clearRect(0,0,w,h);
    ctx.lineWidth = 1;
    ctx.strokeStyle = "black";
    ctx.beginPath();
    ctx.arc(mouse.x,mouse.y,mouseClear,0,PI2);
    ctx.miterLimit = 1000;
    ctx.stroke();
    for(var i = 0; i < numArrows; i ++){
        var x = cw + Math.cos((i/numArrows)*PI2) * cw *1.8;
        var y = ch + Math.sin((i/numArrows)*PI2) * ch *1.8;
        var dir = Math.atan2(y-mouse.y,x-mouse.x);
        var xx = mouse.x + Math.cos(dir) * mouseClear;
        var yy = mouse.y + Math.sin(dir) * mouseClear;
        var scaleLine = (Math.sin(globalTime/1000)+1.1) * 2;
        var style = arrows[i%arrows.length];
        var arrowHead = (Math.sin(globalTime/770)+1.1) * 2;
        var arrowSize = (Math.sin(globalTime/1370)+1.1) * 2;
        ctx.lineWidth = style.lineWidth * scaleLine;
        ctx.strokeStyle = style.line;
        ctx.fillStyle = style.fill;
        ctx.lineJoin = style.join;
        tempArrow.headWidth = style.headSize * arrowHead; 
        tempArrow.headSize = style.headSize * arrowSize;
        
        
        drawArrow(ctx,x,y,xx,yy,tempArrow);
    }

}






//==============================================================================
// From here down part of answer just boiler room stuff
// can be ignored.













/** SimpleFullCanvasMouse.js begin **/

var w, h, cw, ch, canvas, ctx, mouse, globalTime = 0, firstRun = true;



;(function(){
    const RESIZE_DEBOUNCE_TIME = 100;
    var  createCanvas, resizeCanvas, setGlobals, resizeCount = 0;
    createCanvas = function () {
        var c,
        cs;
        cs = (c = document.createElement("canvas")).style;
        cs.position = "absolute";
        cs.top = cs.left = "0px";
        cs.zIndex = 1000;
        document.body.appendChild(c);
        return c;
    }
    resizeCanvas = function () {
        if (canvas === undefined) {
            canvas = createCanvas();
        }
        canvas.width = innerWidth;
        canvas.height = innerHeight;
        ctx = canvas.getContext("2d");
        if (typeof setGlobals === "function") {
            setGlobals();
        }
        if (typeof onResize === "function") {
            if(firstRun){
                onResize();
                firstRun = false;
            }else{
                resizeCount += 1;
                setTimeout(debounceResize, RESIZE_DEBOUNCE_TIME);
            }
        }
    }
    function debounceResize() {
        resizeCount -= 1;
        if (resizeCount <= 0) {
            onResize();
        }
    }
    setGlobals = function () {
        cw = (w = canvas.width) / 2;
        ch = (h = canvas.height) / 2;
    }
    mouse = (function () {
        function preventDefault(e) {
            e.preventDefault();
        }
        var mouse = {
            x : 0,
            y : 0,
            w : 0,
            alt : false,
            shift : false,
            ctrl : false,
            buttonRaw : 0,
            over : false,
            bm : [1, 2, 4, 6, 5, 3],
            active : false,
            bounds : null,
            crashRecover : null,
            mouseEvents : "mousemove,mousedown,mouseup,mouseout,mouseover,mousewheel,DOMMouseScroll".split(",")
        };
        var m = mouse;
        function mouseMove(e) {
            var t = e.type;
            m.bounds = m.element.getBoundingClientRect();
            m.x = e.pageX - m.bounds.left;
            m.y = e.pageY - m.bounds.top;
            m.alt = e.altKey;
            m.shift = e.shiftKey;
            m.ctrl = e.ctrlKey;
            if (t === "mousedown") {
                m.buttonRaw |= m.bm[e.which - 1];
            } else if (t === "mouseup") {
                m.buttonRaw &= m.bm[e.which + 2];
            } else if (t === "mouseout") {
                m.buttonRaw = 0;
                m.over = false;
            } else if (t === "mouseover") {
                m.over = true;
            } else if (t === "mousewheel") {
                m.w = e.wheelDelta;
            } else if (t === "DOMMouseScroll") {
                m.w = -e.detail;
            }
            if (m.callbacks) {
                m.callbacks.forEach(c => c(e));
            }
            e.preventDefault();
        }
        m.addCallback = function (callback) {
            if (typeof callback === "function") {
                if (m.callbacks === undefined) {
                    m.callbacks = [callback];
                } else {
                    m.callbacks.push(callback);
                }
            }
        }
        m.start = function (element) {
            if (m.element !== undefined) {
                m.removeMouse();
            }
            m.element = element === undefined ? document : element;
            m.mouseEvents.forEach(n => {
                m.element.addEventListener(n, mouseMove);
            });
            m.element.addEventListener("contextmenu", preventDefault, false);
            m.active = true;
        }
        m.remove = function () {
            if (m.element !== undefined) {
                m.mouseEvents.forEach(n => {
                    m.element.removeEventListener(n, mouseMove);
                });
                m.element.removeEventListener("contextmenu", preventDefault);
                m.element = m.callbacks = undefined;
                m.active = false;
            }
        }
        return mouse;
    })();



    function update(timer) { // Main update loop
        if(ctx === undefined){
            return;
        }
        globalTime = timer;
        display(); // call demo code
        requestAnimationFrame(update);
    }
    setTimeout(function(){
        resizeCanvas();
        mouse.start(canvas, true);
        window.addEventListener("resize", resizeCanvas);
        requestAnimationFrame(update);
    },0);
})();