【问题标题】:Javascript canvas arrow - arrowhead not pointing at line endJavascript画布箭头 - 箭头不指向行尾
【发布时间】:2016-12-16 10:33:54
【问题描述】:

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

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();
}

(代码在这个答案中找到:Draw arrow on canvas tag)。

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

我的解决方案是将箭头的初始长度减少 lineWidth 的量,但我没有考虑它的箭头角度。

【问题讨论】:

    标签: javascript html canvas


    【解决方案1】:

    只需一点点三角即可。 Bellow 是一个解决 3 种类型的线连接的 sn-p。斜角、斜接和圆形。

    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);
    })();
    
    
     

    【讨论】:

    • 为一个好的答案点赞……还有一个简洁的小动画说明答案[微笑]。
    猜你喜欢
    • 1970-01-01
    • 2012-05-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-09-07
    • 1970-01-01
    • 2019-07-26
    相关资源
    最近更新 更多