先上效果图:
根据两点坐标,计算连线与坐标轴间的夹角(弧度、角度)(整理)
代码
<!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 实现跑马灯(流动)效果
角度与弧度互转
角度与弧度互转