【问题标题】:Canvas quadraticCurveToCanvas quadraticCurveTo
【发布时间】:2023-04-06 16:53:01
【问题描述】:

我发现这段代码会生成一个带圆角的矩形,但我希望能够根据需要增加矩形的大小(高度和宽度)。

 var canvas = document.getElementById('newCanvas');
 var ctx = canvas.getContext('2d');
 ctx.beginPath();
 ctx.moveTo(20, 10);
 ctx.lineTo(80, 10);
 ctx.quadraticCurveTo(90, 10, 90, 20);
 ctx.lineTo(90, 80);
 ctx.quadraticCurveTo(90, 90, 80, 90);
 ctx.lineTo(20, 90);
 ctx.quadraticCurveTo(10, 90, 10, 80);
 ctx.lineTo(10, 20);
 ctx.quadraticCurveTo(10, 10, 20, 10);
 ctx.stroke();

【问题讨论】:

    标签: javascript html5-canvas


    【解决方案1】:

    别忘了加入路径末端

    我注意到您忘记关闭路径。根据ctx.lineJoin 设置,这可能会导致路径开始/结束处出现轻微的接缝或凹凸。

    ctx.closePath 的调用用一条线连接结束和开始

    视觉设计

    要使用的曲线类型的视觉设计规则。

    • 贝塞尔曲线表示快速移动的事物的一部分
    • 圆圈表示静止或缓慢移动的物体

    贝塞尔曲线永远不能完全适合一个圆。二次贝塞尔曲线非常不适合。如果您必须使用贝塞尔曲线,请使用三次贝塞尔曲线以获得更好的拟合。

    使用三次贝塞尔曲线的最佳approximation of a circle 是将控制点插入c = 0.55191502449 作为半径的分数。这将导致可能的最小径向误差为 0.019608%

    示例显示三次(黑色)和二次(红色)曲线之间的差异。

    const ctx = canvas.getContext('2d');
    ctx.strokeStyle = 'red';
    drawRoundedRectQuad(ctx, 10, 10, 180, 180, 70);
    ctx.strokeStyle = 'black';
    drawRoundedRect(ctx, 10, 10, 180, 180, 70);
    
    function drawRoundedRect(ctx, x, y, w, h, r) {
        const c = 0.55191502449;
        const cP = r * (1 - c);
        const right = x + w;
        const bottom = y + h;
        ctx.beginPath();
        ctx.lineTo(right - r, y);
        ctx.bezierCurveTo(right - cP, y, right, y + cP, right, y + r);
        ctx.lineTo(right, bottom - r);
        ctx.bezierCurveTo(right, bottom - cP, right - cP, bottom, right - r, bottom);
        ctx.lineTo(x + r, bottom);
        ctx.bezierCurveTo(x + cP, bottom, x, bottom - cP, x,  bottom - r);
        ctx.lineTo(x, y + r);
        ctx.bezierCurveTo(x, y + cP , x + cP, y, x + r, y);
        ctx.closePath();
        ctx.stroke();
    }
    function drawRoundedRectQuad(ctx, x, y, w, h, r){
        ctx.beginPath();
        ctx.lineTo(x + w- r, y);
        ctx.quadraticCurveTo(x + w, y, x + w, y + r);
        ctx.lineTo(x + w, y + h- r);
        ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
        ctx.lineTo(x + r, y + h);
        ctx.quadraticCurveTo(x, y + h, x, y + h- r);
        ctx.lineTo(x, y + r);
        ctx.quadraticCurveTo(x, y, x + r, y);
        ctx.closePath();
        ctx.stroke();
    };
    <canvas id="canvas" width ="200" height="200"></canvas>

    圆角以匹配 CSS border-radius

    要获得真正的圆角矩形(圆形而不是近似曲线),请使用ctx.arc 创建圆角。

    使用roundedRect 扩展 2D API

    下面的代码通过将函数strokeRoundedRect(x, y, w, [h, [r]])fillRoundedRect(x, y, w, [h, [r]])roundedRect(x, y, w, [h, [r]])添加到2D上下文原型来绘制一个圆角矩形。

    参数

    x, y, w, [h, [r]]

    • x, y 圆角矩形的左上角
    • w,圆角矩形的宽度
    • h,矩形的可选高度。默认为宽度值(创建圆角正方形)
    • r 可选半径或拐角。默认值为 0(无圆角)。如果值为负,则使用半径 0。如果 r > 大于宽度或高度的一半,则 r 更改为 Math.min(w * 0.5, h * 0.5)

    示例

    包括圆角矩形扩展的实现。

    function Extend2DRoundedRect() {
        const p90  = Math.PI * 0.5;
        const p180 = Math.PI;
        const p270 = Math.PI * 1.5;
        const p360 = Math.PI * 2;
        function roundedRect(x, y, w, h = w, r = 0) {
            const ctx = this;
            if (r < 0) { r = 0 }
            if (r === 0) {
                ctx.rect(x, y, w, h);
                return;
            }
            r = Math.min(r, w * 0.5, h * 0.5)
            ctx.moveTo(x, y + r);   
            ctx.arc(x + r    , y + r    , r, p180, p270);
            ctx.arc(x + w - r, y + r    , r, p270, p360);
            ctx.arc(x + w - r, y + h - r, r, 0   , p90);
            ctx.arc(x + r    , y + h - r, r, p90 , p180);
            ctx.closePath();
        }
        function strokeRoundedRect(...args) {
            const ctx = this;
            ctx.beginPath();
            ctx.roundedRect(...args);
            ctx.stroke();
        }
        function fillRoundedRect(...args) {
            const ctx = this;        
            ctx.beginPath();
            ctx.roundedRect(...args);
            ctx.fill();
        }
        CanvasRenderingContext2D.prototype.roundedRect = roundedRect;
        CanvasRenderingContext2D.prototype.strokeRoundedRect = strokeRoundedRect;
        CanvasRenderingContext2D.prototype.fillRoundedRect = fillRoundedRect;
    }
    Extend2DRoundedRect();
    
    
    // Using rounded rectangle extended 2D context
    const ctx = canvas.getContext('2d');
    ctx.strokeStyle = "#000";
    ctx.strokeRoundedRect(10.5, 10.5, 180, 180);      // no radius render rectangle
    ctx.strokeRoundedRect(210.5, 10.5, 180, 180, 20); // Draw 1px line along center of pixels
    ctx.strokeRoundedRect(20, 20, 160, 160, 30);  
    ctx.fillRoundedRect(30, 30, 140, 140, 20);  
    
    ctx.fillRoundedRect(230, 30, 140, 40, 20);  // Circle ends
    ctx.fillRoundedRect(230, 80, 140, 20, 20);  // Auto circle ends
    ctx.fillRoundedRect(280, 120, 40, 40, 120); // circle all sides
    
    var inset = 0;
    ctx.beginPath();
    while (inset < 80) {
        ctx.roundedRect(
            10 + inset, 210 + inset, 
            380 - inset * 2, 180 - inset * 2, 
            50 - inset
         );
         inset += 8;
    }
    ctx.fill("evenodd");
    &lt;canvas id="canvas" width="400" height="400"&gt;&lt;/canvas&gt;

    【讨论】:

    • 请注意,roundRect 可能很快就会成为规范的一部分(它已经在 Chrome 中的 chrome://flags/#enable-experimental-web-platform-features 下)。您可能希望对该 API 进行功能检测和 polyfill,而不是编写另一个 API。此外,为了保持 fillRectstrokeRect 的行为,您可能更喜欢使用 Path2D 对象而不是破坏上下文的当前路径。
    • @Kaiido 感谢您的提醒。没有冲突,因为我使用 rounded 而不是 round 我希望 joinRadius 具有 convexJoinRadiusconcaveJoinRadius(对于封闭路径)属性和/或类似于 setLineDash 的调用,例如 setCornerRadius([r1, r2, ..., rn])
    • 是的,没有冲突,它只是为了未来,对读者来说,插入代码并以与本机实现相同的方式使用它可能会变得更有用。对于 joinRadius 的想法,我想您可以在github.com/fserb/canvas2D/issues 上进行讨论,我不太确定您想要控制lineJoin="round" 的半径还是其他东西。但是感谢凹点,我现在看到 Chrome 的当前实现不处理凹角......听起来它应该在那里......
    • @Kaiido 参见 sn-p bottom stackoverflow.com/a/44856925/3877726 作为舍入连接的示例(在每个连接处添加一个弧)。这是一个相当糟糕的实现,因为它可以使用arcTo 用更少的数学来完成,就像在stackoverflow.com/a/63056660/3877726 中实现的那样。全局连接弧半径将允许在涉及线、曲线和弧的连接处使用圆角,例如饼图圆角看起来很漂亮,但数学很复杂
    【解决方案2】:

    您需要将静态值转换为(x, y) 坐标和[width × height] 维度变量。

    我采用了你所拥有的,并对公式进行了逆向工程来计算你的静态绘图。获取现有变量并将它们更改为 xy 并将 widthheight 添加到它们,并在必要时选择添加或减去 radius

    const drawRoundedRect = (ctx, x, y, width, height, radius) => {
      ctx.beginPath();
      ctx.moveTo(x + radius, y);
      ctx.lineTo(x + width - radius, y);
      ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
      ctx.lineTo(x + width, y + height - radius);
      ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
      ctx.lineTo(x + radius, y + height);
      ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
      ctx.lineTo(x, y + radius);
      ctx.quadraticCurveTo(x, y, x + radius, y);
      ctx.stroke();
    };
    
    const canvas = document.getElementById('new-canvas');
    const ctx = canvas.getContext('2d');
    
    ctx.strokeStyle = 'black';
    ctx.strokeRect(10, 10, 80, 80);
    
    ctx.strokeStyle = 'red';
    drawRoundedRect(ctx, 10, 10, 80, 80, 10);
    
    ctx.strokeStyle = 'green';
    drawRoundedRect(ctx, 20, 20, 60, 60, 14);
    &lt;canvas id="new-canvas"&gt;&lt;/canvas&gt;

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-08-16
      • 1970-01-01
      • 2012-06-22
      • 1970-01-01
      • 1970-01-01
      • 2012-02-01
      相关资源
      最近更新 更多