Canvas箭头动画

先上效果图:



代码

<!DOCTYPE html>
<html>
<head>
    <title>箭头动画</title>
</head>
<body>
    <canvas id="myCanvas">Your browser does not support HTML5 Canvas. </canvas>
</body>
</html>
<script type="text/javascript">
    var myCanvas = document.getElementById("myCanvas");
    myCanvas.style.cssText = "position:absolute;left:0;top:0; border: 1px solid #ccc;"; // 画布样式
    myCanvas.width = 600;   // 画布的宽度
    myCanvas.height = 600;  // 画布的高度 


    // 画笔(绘图对象)
    var ctx = myCanvas.getContext('2d');


    var deg = 30;
    var hud = deg * (Math.PI / 180);
    var r = 100;        // 半径 (即两点距离)
    var p1 = {
        x: 300,         // X坐标
        y: 150          // y坐标
    }
    var p2 = {
        x: p1.x + Math.cos(hud) * r,   // X坐标
        y: p1.y - Math.sin(hud) * r    // y坐标    
    }


    // 动画效果
    var _index = 1;
    setInterval(function () {
        // 清空画布
        var BW = myCanvas.width;
        var BH = myCanvas.height;
        ctx.clearRect(0, 0, BW, BH);                // 清空画布


        // 用于参考倾斜度的直线
        ctx.strokeStyle = "red";
        ctx.beginPath()
        ctx.moveTo(p1.x, p1.y);
        ctx.lineTo(p2.x, p2.y);
        ctx.stroke();
        ctx.closePath();


        // 无效果
        arrowTo(ctx, p1, p2, { color: "blue" });


        // 高亮效果
        arrowTo(ctx, { x: 100, y: 150 }, { x: 250, y: 450 }, { activeIndex: _index });
        arrowTo(ctx, { x: 200, y: 250 }, { x: 450, y: 250 }, { activeIndex: _index });
        arrowTo(ctx, { x: 500, y: 50 }, { x: 450, y: 250 }, { activeIndex: _index });
        arrowTo(ctx, { x: 200, y: 250 }, { x: 200, y: 50 }, { activeIndex: _index });


        // 整体偏移效果
        var offset = (_index % 3) * 5;
        arrowTo(ctx, { x: 200, y: 400 }, { x: 500, y: 400 }, { offset: offset, color: "red", justifyAlign: false });
        arrowTo(ctx, { x: 500, y: 400 }, { x: 450, y: 580 }, { offset: offset, color: "red", justifyAlign: false });
        arrowTo(ctx, { x: 450, y: 580 }, { x: 160, y: 580 }, { offset: offset, color: "red", justifyAlign: false });
        arrowTo(ctx, { x: 160, y: 580 }, { x: 200, y: 400 }, { offset: offset, color: "red", justifyAlign: false });


        // 其他处理
        if (_index >= 50) {
            _index = 1
        }
        else {
            _index++;
        }
    }, 300);
</script>


<script type="text/javascript">
    // 画两点间箭头
    // ctx - 画布上下文(我理解成画笔)
    // p1 - 起点
    // p2 - 终点
    // arrowOptions -- 画箭头的选项
    function arrowTo(ctx, p1, p2, arrowOptions) {
        // 初始化参数
        var opts = {
            startOffset: 5,             // 起点的留空长度
            endOffset: 5,               // 终点的留空长度
            offset: 0,                  // 偏移位(模拟动画效果用, 使用时建议将justifyAlign设为false) 
            color: '#E6E6FA',           // 默认颜色 
            activeIndex: -1,            // 高亮箭头的索引, 超出回到一圈起始位置。(默认-1,不做高亮处理) 
            activeColor: "#00FF00",     // 高亮颜色(Highligh Color)
            stepLength: 10,             // 间隔(步长)
            justifyAlign: true,         // 两端对齐(两边撑满, 配合activeIndex > 0时使用) 
            arrowLength: 15,            // 箭头长度(柄到顶点)            
            arrowTheta: 25,             // 箭头两边的夹角(度数) 
            arrowHeadlen: 6,            // 箭头两边斜边长度 
            arrowLineWidth: 1,          // 画箭头的线宽度
            lineWidth: 1,               // 两点间的连丝宽度(>0时,有效)
        };


        if (arrowOptions !== undefined && arrowOptions !== null) {
            opts = Object.assign(opts, arrowOptions);
        }


        // 画连结两点的线
        if (opts.lineWidth > 0) {
            ctx.beginPath();
            ctx.moveTo(p1.x, p1.y);
            ctx.lineTo(p2.x, p2.y);
            //颜色,线宽
            ctx.strokeStyle = opts.color;
            ctx.lineWidth = opts.lineWidth;
            ctx.stroke();
            ctx.closePath();
        }


        // 计两点距离
        var len = Math.floor(Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)));


        // 计算画多少个箭头(注意:最后一个箭头是不需要间隔(步长),所以可用长度要加一个opts.stepLength)
        var loops = Math.floor((len - (opts.startOffset + opts.offset + opts.endOffset) + opts.stepLength) / (opts.arrowLength + opts.stepLength));


        // 两端对齐(两边撑满),重算步长 
        if (opts.justifyAlign === true) {
            opts.stepLength = (len - (opts.startOffset + opts.offset + opts.endOffset) - (opts.arrowLength * loops)) / (loops - 1);
        }


        // 高亮箭头的索引, 超出回到一圈起始位置。(用于动画效果) 
        var highlightIndex = 0;               // 0 - 无动画效果                
        if (opts.activeIndex > 0) {
            if ((opts.activeIndex % loops) === 0) {
                highlightIndex = loops;
            }
            else {
                highlightIndex = opts.activeIndex % loops;
            }
        }


        var hudu = Math.atan2(p1.y - p2.y, p2.x - p1.x);    // 计算p1, p2两点的倾斜度(弧度)。(注意参数:p1.y - p2.y, p2.x - p1.x, 请勿搞错)
        var p0 = { x: p1.x, y: p1.y };                      // 原点坐标, 作为圆心。 (辅助计算箭头起点(柄)与顶点的坐标)
        var r;                                              // 半径。 (辅助计算箭头起点(柄)与顶点的坐标)
        var color;
        for (var i = 0; i < loops; i++) {
            // 箭头起点(柄)
            r = (opts.startOffset + opts.offset) + (opts.arrowLength + opts.stepLength) * i;     // 原点到箭头起点(柄)的半径
            p1 = {
                x: p0.x + Math.cos(hudu) * r,
                y: p0.y - Math.sin(hudu) * r
            };


            // 箭头终点(顶点)
            r = r + opts.arrowLength;                       // 原点到箭头顶点(柄)的半径
            p2 = {
                x: p0.x + Math.cos(hudu) * r,
                y: p0.y - Math.sin(hudu) * r
            };


            // 画一个箭头
            if (highlightIndex > 0 && i === (highlightIndex - 1)) {
                color = opts.activeColor;       //高亮箭头(动画效果)
            }
            else {
                color = opts.color;
            }
            drawArrow(ctx, p1, p2, opts.arrowTheta, opts.arrowHeadlen, opts.arrowLineWidth, color);
        }
    }


    // 画箭头
    // ctx - 画布上下文(我理解成画笔)
    // p1 - 起点
    // p2 - 终点
    // theta -- 夹角theta (是度数,不是弧度)
    // headlen -- 斜边长度
    // width -- 线宽
    // color -- 颜色
    function drawArrow(ctx, p1, p2, theta, headlen, width, color) {
        theta = (theta !== undefined && theta !== null) ? theta : 25;       //夹角(度数)
        headlen = (headlen !== undefined && headlen !== null) ? headlen : 6;   //斜边长度
        width = (width !== undefined && width !== null) ? width : 1;        //线宽
        color = (color !== undefined && color !== null) ? color : '#000';       //颜色


        var angle = Math.atan2(p1.y - p2.y, p1.x - p2.x) * 180 / Math.PI,   //倾斜度(度数)
            angle1 = (angle + theta) * Math.PI / 180,            //夹角1
            angle2 = (angle - theta) * Math.PI / 180,            //夹角2   
            topX = headlen * Math.cos(angle1),                   //箭头上面点, X偏移位
            topY = headlen * Math.sin(angle1),                   //箭头上面点, Y偏移位
            botX = headlen * Math.cos(angle2),                   //箭头下面点, X偏移位   
            botY = headlen * Math.sin(angle2);                   //箭头下面点, Y偏移位      


        ctx.save();


        ctx.beginPath();


        //连结两点的线
        ctx.moveTo(p1.x, p1.y);
        ctx.lineTo(p2.x, p2.y);


        //终点箭头的两侧
        var arrowX = p2.x + topX;
        var arrowY = p2.y + topY;
        ctx.moveTo(arrowX, arrowY);
        ctx.lineTo(p2.x, p2.y);           //终点
        arrowX = p2.x + botX;
        arrowY = p2.y + botY;
        ctx.lineTo(arrowX, arrowY);


        //颜色,线宽
        ctx.strokeStyle = color;
        ctx.lineWidth = width;
        ctx.stroke();


        ctx.restore();
    }


    // 为Object扩展assign方法(合拼多个对象的属性,返回一个新对象)
    if (!Object.assign) {
        Object.defineProperty(Object, "assign", {
            enumerable: false,
            configurable: true,
            writable: true,
            value: function (target, firstSource) {
                "use strict";
                if (target === undefined || target === null)
                    throw new TypeError("Cannot convert first argument to object");
                var to = Object(target);
                for (var i = 1; i < arguments.length; i++) {
                    var nextSource = arguments[i];
                    if (nextSource === undefined || nextSource === null) continue;
                    var keysArray = Object.keys(Object(nextSource));
                    for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
                        var nextKey = keysArray[nextIndex];
                        var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
                        if (desc !== undefined && desc.enumerable) to[nextKey] = nextSource[nextKey];
                    }
                }
                return to;
            }
        });
    }
</script>
参考资料:
根据两点坐标,计算连线与坐标轴间的夹角(弧度、角度)(整理)

Canvas学习:绘制箭头
利用setLineDash 和 lineDashOffset 实现跑马灯(流动)效果
角度与弧度互转


角度与弧度互转

猜你喜欢

转载自blog.csdn.net/chelen_jak/article/details/80520882
今日推荐