You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Canvas绘制加拿大国旗枫叶时如何在直线交点处生成指定角度曲线

Canvas 绘制加拿大国旗枫叶圆角过渡方案

核心解决思路

你当前代码全部采用直线连接顶点,导致拐点为尖角,要生成夹角范围内的指定过渡圆弧,可通过计算拐点处的圆角路径实现,步骤如下:

  • 确定所有需要添加过渡圆弧的拐点坐标,以及每个拐点对应的圆角半径,可按枫叶宽高比例换算得到,当前方案取宽度的1.2%适配官方构造规范,可按需调整
  • 对每个需要处理的拐点,获取其前一个相邻点、拐点本身、后一个相邻点三个坐标,计算夹角方向的圆角起点、终点、圆心和角度范围
  • 绘制路径时跳过原拐点的直线连接操作,替换为绘制对应范围内的圆弧

优化后完整代码

function drawMapleLeaf (ctx,x,y,width,height) {
    let rx = width;
    let ry = height;
    let xc = x + rx / 2;
    let yc = y + height;
    // 圆角半径,可根据实际效果调整
    const cornerRadius = rx * 0.012;

    let points = [];
    points.push([xc + rx * 0.021423, yc - ry * 0.215686]);
    points.push([xc + rx * 0.270780, yc - ry * 0.203804]);
    points.push([xc + rx * 0.271820, yc - ry * 0.295752]);
    points.push([xc + rx * 0.482015, yc - ry * 0.411765]);
    points.push([xc + rx * 0.443046, yc - ry * 0.483267]);
    points.push([xc + rx * 0.500000, yc - ry * 0.587435]);
    points.push([xc + rx * 0.363353, yc - ry * 0.619576]);
    points.push([xc + rx * 0.342287, yc - ry * 0.693849]);
    points.push([xc + rx * 0.153596, yc - ry * 0.612537]);
    points.push([xc + rx * 0.201601, yc - ry * 0.918462]);
    points.push([xc + rx * 0.093001, yc - ry * 0.894514]);
    points.push([xc, yc - ry]);
    points.push([xc - rx * 0.093001, yc - ry * 0.894514]);
    points.push([xc - rx * 0.201601, yc - ry * 0.918462]);
    points.push([xc - rx * 0.153596, yc - ry * 0.612537]);
    points.push([xc - rx * 0.342287, yc - ry * 0.693849]);
    points.push([xc - rx * 0.363353, yc - ry * 0.619576]);
    points.push([xc - rx * 0.500000, yc - ry * 0.587435]);
    points.push([xc - rx * 0.443046, yc - ry * 0.483267]);
    points.push([xc - rx * 0.482015, yc - ry * 0.411765]);
    points.push([xc - rx * 0.271820, yc - ry * 0.295752]);
    points.push([xc - rx * 0.2707796, yc - ry * 0.203804]);
    points.push([xc - rx * 0.021423, yc - ry * 0.215686]);
    points.push([xc - rx * 0.021423, yc]);
    points.push([xc + rx * 0.021423, yc]);
    points.push([xc + rx * 0.021423, yc - ry * 0.215686]);

    // 计算两点距离
    function dist(p1, p2) {
        return Math.hypot(p2[0] - p1[0], p2[1] - p1[1]);
    }
    // 计算单位向量
    function unitVector(p1, p2) {
        let d = dist(p1, p2);
        return [(p2[0] - p1[0])/d, (p2[1] - p1[1])/d];
    }
    // 计算两点连线角度
    function angle(p1, p2) {
        return Math.atan2(p2[1] - p1[1], p2[0] - p1[0]);
    }

    ctx.beginPath();
    ctx.moveTo(points[0][0], points[0][1]);
    for(let i=1;i<points.length-1;i++){
        let p0 = points[i-1];
        let p1 = points[i];
        let p2 = points[i+1];
        // 计算两个边的单位向量
        let v1 = unitVector(p1, p0);
        let v2 = unitVector(p1, p2);
        // 计算夹角半角
        let halfAngle = Math.acos((v1[0]*v2[0] + v1[1]*v2[1])/2);
        // 计算圆角到顶点的距离
        let distToVertex = cornerRadius / Math.sin(halfAngle);
        // 计算圆角起点和终点
        let startPoint = [p1[0] + v1[0] * distToVertex, p1[1] + v1[1] * distToVertex];
        let endPoint = [p1[0] + v2[0] * distToVertex, p1[1] + v2[1] * distToVertex];
        // 计算角平分线方向
        let bisector = [(v1[0] + v2[0])/2, (v1[1] + v2[1])/2];
        let bisectorLen = Math.hypot(bisector[0], bisector[1]);
        bisector = [bisector[0]/bisectorLen, bisector[1]/bisectorLen];
        // 计算圆心坐标
        let center = [p1[0] + bisector[0] * distToVertex * Math.cos(halfAngle), p1[1] + bisector[1] * distToVertex * Math.cos(halfAngle)];
        // 计算圆弧起始角度和结束角度
        let startAngle = angle(center, startPoint);
        let endAngle = angle(center, endPoint);
        // 绘制圆弧,逆时针绘制符合枫叶路径方向
        ctx.arc(center[0], center[1], cornerRadius, startAngle, endAngle, true);
    }
    ctx.closePath();
    ctx.fillStyle='red';
    ctx.fill();
}

生成的过渡圆弧仅存在于两条直线的夹角范围内,完全符合构造图指定的角度要求。

内容的提问来源于stack exchange,提问作者michael

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.23 23:06:04