RError.com

RError.com Logo RError.com Logo

RError.com Navigation

  • 主页

Mobile menu

Close
  • 主页
  • 系统&网络
    • 热门问题
    • 最新问题
    • 标签
  • Ubuntu
    • 热门问题
    • 最新问题
    • 标签
  • 帮助
主页 / 问题 / 1360564
Accepted
Alexandr_TT
Alexandr_TT
Asked:2022-05-13 23:11:35 +0000 UTC2022-05-13 23:11:35 +0000 UTC 2022-05-13 23:11:35 +0000 UTC

如何使用 HTML Canvas 绘制圆角矩形?

  • 772

HTML Canvas 提供了绘制矩形fillRect()和的方法strokeRect(),但我找不到创建圆角矩形的方法。我怎样才能做到这一点?

自由翻译问题How to draw a rounded rectangle using HTML Canvas? @ DNB5brims。

html
  • 5 5 个回答
  • 10 Views

5 个回答

  • Voted
  1. Grundy
    2022-05-14T00:10:35Z2022-05-14T00:10:35Z

    另一种选择:使用.arcTo允许您绘制特定弧线的选项

    如果我们考虑角的半径,可以得出以下函数

    var canvas = document.getElementById("myCanvas");
    var ctx = canvas.getContext('2d');
    
    function roundRect(ctx, x1, y1, x2, y2, radius) {
      radius = Math.min(radius, (x2 - x1) / 2, (y2 - y1) / 2); // избегаем артефактов, в случае если радиус скругления больше одной из сторон
      ctx.beginPath();
      ctx.moveTo(x1 + radius, y1);
      ctx.lineTo(x2 - radius, y1);
      ctx.arcTo(x2, y1, x2, y1 + radius, radius);
      ctx.lineTo(x2, y2 - radius);
      ctx.arcTo(x2, y2, x2 - radius, y2, radius);
      ctx.lineTo(x1 + radius, y2);
      ctx.arcTo(x1, y2, x1, y2 - radius, radius);
      ctx.lineTo(x1, y1 + radius);
      ctx.arcTo(x1, y1, x1 + radius, y1, radius);
      ctx.stroke();
    }
    
    function yop() {
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      ctx.fillStyle = "#0000ff";
      ctx.strokeStyle = "#ff0000";
    
      roundRect(ctx, 10, 10, 200, 160, +radius.value);
      requestAnimationFrame(yop);
    }
    
    
    requestAnimationFrame(yop);
    <input type="range" min="1" max="75" id="radius">
    
    <canvas id="myCanvas" width="600" height="400">
    </canvas>

    • 3
  2. Alexandr_TT
    2022-05-13T23:11:35Z2022-05-13T23:11:35Z

    所以这是基于使用lineJoin = "round"和正确的比例,数学和逻辑,我能够制作这个功能,它并不完美,但希望它有所帮助。如果您希望每个角具有不同的半径,请查看:https ://p5js.org/reference/#/p5/rect

    就这样:

    CanvasRenderingContext2D.prototype.roundRect = function (x,y,width,height,radius) {
        radius = Math.min(Math.max(width-1,1),Math.max(height-1,1),radius);
        var rectX = x;
        var rectY = y;
        var rectWidth = width;
        var rectHeight = height;
        var cornerRadius = radius;
    
        this.lineJoin = "round";
        this.lineWidth = cornerRadius;
        this.strokeRect(rectX+(cornerRadius/2), rectY+(cornerRadius/2), rectWidth-cornerRadius, rectHeight-cornerRadius);
        this.fillRect(rectX+(cornerRadius/2), rectY+(cornerRadius/2), rectWidth-cornerRadius, rectHeight-cornerRadius);
        this.stroke();
        this.fill();
    }
    

    下面是完整的代码:

    CanvasRenderingContext2D.prototype.roundRect = function (x,y,width,height,radius) {
        radius = Math.min(Math.max(width-1,1),Math.max(height-1,1),radius);
        var rectX = x;
        var rectY = y;
        var rectWidth = width;
        var rectHeight = height;
        var cornerRadius = radius;
    
        this.lineJoin = "round";
        this.lineWidth = cornerRadius;
        this.strokeRect(rectX+(cornerRadius/2), rectY+(cornerRadius/2), rectWidth-cornerRadius, rectHeight-cornerRadius);
        this.fillRect(rectX+(cornerRadius/2), rectY+(cornerRadius/2), rectWidth-cornerRadius, rectHeight-cornerRadius);
        this.stroke();
        this.fill();
    }
        var canvas = document.getElementById("myCanvas");
        var ctx = canvas.getContext('2d');
    function yop() {
      ctx.clearRect(0,0,1000,1000)
      ctx.fillStyle = "#ff0000";
      ctx.strokeStyle = "#ff0000";  ctx.roundRect(Number(document.getElementById("myRange1").value),Number(document.getElementById("myRange2").value),Number(document.getElementById("myRange3").value),Number(document.getElementById("myRange4").value),Number(document.getElementById("myRange5").value));
    requestAnimationFrame(yop);
    }
    requestAnimationFrame(yop);
    <input type="range" min="0" max="1000" value="10" class="slider" id="myRange1">  
    <input type="range" min="0" max="1000" value="10" class="slider" id="myRange2">   
    <input type="range" min="0" max="1000" value="200" class="slider" id="myRange3">   
    <input type="range" min="0" max="1000" value="100" class="slider" id="myRange4"><input type="range" min="1" max="1000" value="50" class="slider" id="myRange5">
    <canvas id="myCanvas" width="1000" height="1000">
    </canvas>

    来自贡献者 @Woold的答案的松散翻译 。

    • 2
  3. Best Answer
    Leonid
    2022-05-14T01:23:06Z2022-05-14T01:23:06Z

    你可以生成这个矩形的路径,然后对它应用strokeor fill。我检查了半径,使其不超过矩形较小边的一半。

    该函数可以针对不同的半径重写,只需将r每一行替换为 r[0] (右上角的顶行),依此类推。

    const canvas = document.querySelector('canvas');
    const ctx = canvas.getContext('2d');
    
    ctx.stroke(new Path2D(roundedRectPath(10,10,100,80,20)));
    ctx.fill(new Path2D(roundedRectPath(150,10,120,60,30)));
    
    
    function roundedRectPath(x,y,w,h,r){
        r = (Math.min(w,h)/2 > r)? r : Math.min(w,h)/2;
        return `M ${x + r} ${y} l ${w-2*r} 0 q ${r} 0 ${r} ${r}
            l 0 ${h-2*r} q 0 ${r} ${-r} ${r}
            l ${-w+2*r} 0 q ${-r} 0 ${-r} ${-r}
            l 0 ${-h+2*r} q 0 ${-r} ${r} ${-r}`;
    }
    <canvas></canvas>

    通过添加一个由 1、2 和 4 值组成的数组(如果有两个,则首先是右边的,然后是左边的),为角添加了不同半径的可能性。我只检查一个侧面的一半是否超过 1 个值。

    const canvas = document.querySelector('canvas');
    const ctx = canvas.getContext('2d');
    canvas.width = 600;
    canvas.height = 180;
    
    ctx.stroke(new Path2D(roundedRectPath(10,10,100,80,[10,20,30,40])));
    ctx.fill(new Path2D(roundedRectPath(150,10,120,60,[30,10])));
    
    ctx.fillStyle = 'red';
    ctx.fill(new Path2D(roundedRectPath(280,10,80,150,[200])));
    
    ctx.strokeStyle = 'purple';
    ctx.stroke(new Path2D(roundedRectPath(380,60,150,30,20)));
    
    
    function roundedRectPath(x,y,w,h,r){
        if(!Array.isArray(r)){
            r = (Math.min(w,h)/2 > r)? r : Math.min(w,h)/2;
            r = [r,r,r,r];
        } else {
            if(r.length == 1){
                r = (Math.min(w,h)/2 > r[0])? r[0] : Math.min(w,h)/2;
                r = [r,r,r,r];
            } else if(r.length == 2){
                r = [r[0], r[0], r[1], r[1]];
            }
        }
        
        return `M ${x + r[3]} ${y} l ${w-r[3]-r[0]} 0 q ${r[0]} 0 ${r[0]} ${r[0]}
            l 0 ${h-r[0]-r[1]} q 0 ${r[1]} ${-r[1]} ${r[1]}
            l ${-w+r[1]+r[2]} 0 q ${-r[2]} 0 ${-r[2]} ${-r[2]}
            l 0 ${-h+r[2]+r[3]} q 0 ${-r[3]} ${r[3]} ${-r[3]}`;
    }
    <canvas></canvas>

    更新

    为了解释代码,我添加了一个常规直角和圆角的示例,其参数可以更改(rx,ry)。这里“native”方法的实现变得清晰roundRect()了,其中最后一个参数radii(radii)可以有多种格式。它可以是 1、2、3 或 4 个数字的列表,也可以是DOMPointInit(由 radius[x] 和 radius[y] 组成)。

    然后,根据某种算法,将传入的数据转换为所谓的。归一化半径列表。[r]转换为[r,r,r,r], [r1,r2] => [r1,r2,r1,r2], [r1,r2,r3] => [r1,r2,r2,r3]. 并且[r1,r2,r3,r4]保持不变。倒计时是从左上角顺时针。(虽然算法在上面的代码中有所不同)。

    因此DOMPointInit,可以表示任何半径。如果只是两个数字的数组,怎么用就不是很明显了,因为在向量路径“移动”的过程中,第一个值并不总是沿着х,所以实际上这是一个复数表示的半径{x:rx, y:ry}。出于同样的原因arcTo,或者arc不能用作舍入,因为它们只有一个半径。此外,不能确保圆弧与直线的延续相切。

    相反,如果控制点与拐角的条件点重合,则二次贝塞尔曲线(q,Q简称SVG путей)将始终提供与该拐角剩余的线相切。rx并且从条件角度为不同的半径ry提供了适当的缩进。这就是下面的代码演示的内容。

    const canvas = document.querySelector('canvas');
    const ctx = canvas.getContext('2d');
    
    const w = canvas.width = 400;
    const h = canvas.height = 180;
    
    drawAngles(100,100);
    
    function drawAngles(rx,ry){
        ctx.clearRect(0,0,w,h);
        
        // A simple angle
        ctx.stroke(new Path2D(`M 10 10 l 160 0 0 160`)); // line has relative coords
        // Draw points of the angle
        drawPoint('red', 4, 10, 10); // The starting point
        drawPoint('red', 4, 170, 10); // The corner point
        drawPoint('red', 4, 170, 170); // The end point
        
        let path = '';
        
        path += `M 200 10 `; // The starting point
        drawPoint('red', 4, 200, 10);
        path += `l ${160 - rx} 0 `; // First straight line (relative coords from the starting point)
        drawPoint('blue', 4, 360 - rx, 10);
        path += `q ${rx} 0 `; // The control point of quadratic Bezier (the corner)
        drawPoint('purple', 4, 360, 10);
        path += `${rx} ${ry} `; // The end point of Bezier curve
        drawPoint('blue', 4, 360, 10 + ry);
        path += `l 0 ${160 - ry}`; // The second straigth line (end point of it)
        drawPoint('red', 4, 360, 170);
        
        ctx.stroke(new Path2D(path));   
    }
    
    function drawPoint(color,radius,cx,cy){
        ctx.save();
        ctx.fillStyle = color;
        ctx.beginPath();
        ctx.arc(cx,cy,radius,0,2*Math.PI,false);
        ctx.fill();
        ctx.restore();
    }
    <canvas></canvas>
    <form oninput="drawAngles(+rx.value,+ry.value)" style="float: right; display:flex; flex-direction: column">
        <input type="range" min="0" max="160" name="rx" value="100">
        <input type="range" min="0" max="160" name="ry" value="100">
    </form>

    • 2
  4. Alexandr_TT
    2022-05-13T23:28:23Z2022-05-13T23:28:23Z

    roundRect(x, y, 宽度, 高度, 半径); 现在正式成为 Canvas 2D API 的一部分。

    它出现在CanvasRenderingContext2D和对象Path2D中OffscreenCanvasRenderingContext2D。

    它的参数radii是一个数组,包含:

    • 一float代表用于所有四个角的半径,
    • 两个分别float用于左上角+右下角和右上角+左下角,
    • 三个分别float代表左上、右上+左下和右下,
    • 或四个float,每个角落一个,
    • 或相同的组合,但用一个对象DOMPointInit表示x-радиус 和y-радиус 每个角。

    目前在 Chrome 中只有一个可用的实现(在仍然不支持 objects 的标志下DOMPointInit,但只有 true DOMPoints),你可以找到我创建的polyfill

    const canvas = document.querySelector("canvas");
    
    const ctx = canvas.getContext("2d");
    ctx.roundRect(20,20,80,80,[new DOMPoint(60,80), new DOMPoint(110,100)]);
    ctx.strokeStyle = "green";
    ctx.stroke();
    
    const path = new Path2D();
    path.roundRect(120,30,60,90,[0,25,new DOMPoint(60,80), new DOMPoint(110,100)]);
    ctx.fillStyle = "purple";
    ctx.fill(path);
    
    // and a simple one
    ctx.beginPath();
    ctx.roundRect(200,20,80,80,[10]);
    ctx.fillStyle = "orange";
    ctx.fill();
    <script src="https://cdn.jsdelivr.net/gh/Kaiido/roundRect/roundRect.js"></script>
    <canvas></canvas>

    来自成员 @Kaiido的答案的松散翻译 。

    • 1
  5. Alexandr_TT
    2022-05-13T23:44:00Z2022-05-13T23:44:00Z

    我需要做同样的事情,我为此创建了一个方法。

    请阅读代码中的注释:

    // Сейчас можно просто вызвать 
    var ctx = document.getElementById("rounded-rect").getContext("2d");
    // Рисование с использованием радиуса границы по умолчанию, 
    // обводка, но без заливки (значения функции по умолчанию)
    roundRect(ctx, 5, 5, 50, 50);
    // Чтобы изменить цвет прямоугольника, просто измените контекст
    ctx.strokeStyle = "rgb(255, 0, 0)";
    ctx.fillStyle = "rgba(255, 255, 0, .5)";
    roundRect(ctx, 100, 5, 100, 100, 20, true);
    // Снова манипулируйте им
    ctx.strokeStyle = "#0f0";
    ctx.fillStyle = "#ddd";
    // Различные радиусы для каждого угла, другие по умолчанию равны 0
    roundRect(ctx, 300, 5, 200, 100, {
      tl: 50,
      br: 25
    }, true);
    
    /**
     * Рисует прямоугольник с закругленными углами, используя текущее состояние холста.
     * Если вы опустите последние три параметра, он будет рисовать прямоугольник
     * контур с радиусом границы 5 пикселей
     * @param {CanvasRenderingContext2D} ctx
     * @param {Number} x The top left x coordinate
     * @param {Number} y The top left y coordinate
     * @param {Number} width The width of the rectangle
     * @param {Number} height The height of the rectangle
     * @param {Number} [radius = 5] The corner radius; It can also be an object 
     *                 to specify different radii for corners
     * @param {Number} [radius.tl = 0] Top left
     * @param {Number} [radius.tr = 0] Top right
     * @param {Number} [radius.br = 0] Bottom right
     * @param {Number} [radius.bl = 0] Bottom left
     * @param {Boolean} [fill = false] Whether to fill the rectangle.
     * @param {Boolean} [stroke = true] Whether to stroke the rectangle.
     */
    function roundRect(ctx, x, y, width, height, radius, fill, stroke) {
      if (typeof stroke === 'undefined') {
        stroke = true;
      }
      if (typeof radius === 'undefined') {
        radius = 5;
      }
      if (typeof radius === 'number') {
        radius = {tl: radius, tr: radius, br: radius, bl: radius};
      } else {
        var defaultRadius = {tl: 0, tr: 0, br: 0, bl: 0};
        for (var side in defaultRadius) {
          radius[side] = radius[side] || defaultRadius[side];
        }
      }
      ctx.beginPath();
      ctx.moveTo(x + radius.tl, y);
      ctx.lineTo(x + width - radius.tr, y);
      ctx.quadraticCurveTo(x + width, y, x + width, y + radius.tr);
      ctx.lineTo(x + width, y + height - radius.br);
      ctx.quadraticCurveTo(x + width, y + height, x + width - radius.br, y + height);
      ctx.lineTo(x + radius.bl, y + height);
      ctx.quadraticCurveTo(x, y + height, x, y + height - radius.bl);
      ctx.lineTo(x, y + radius.tl);
      ctx.quadraticCurveTo(x, y, x + radius.tl, y);
      ctx.closePath();
      if (fill) {
        ctx.fill();
      }
      if (stroke) {
        ctx.stroke();
      }
    
    }
    <canvas id="rounded-rect" width="500" height="200">
      <!-- Insert fallback content here -->
    </canvas>

    贡献者 @Juan Mendes的答案的松散翻译 。

    • 1

相关问题

  • 具有非均匀背景的块内的渐变边框

  • 离开页脚

  • 如何将三个字段的数据收集到一封电子邮件中?

  • Html 元素刚从父元素中出来

  • 如何在css中制作这个背景?

  • 如何制作带有斜条纹的背景?

Sidebar

Stats

  • 问题 10021
  • Answers 30001
  • 最佳答案 8000
  • 用户 6900
  • 常问
  • 回答
  • Marko Smith

    表格填充不起作用

    • 2 个回答
  • Marko Smith

    提示 50/50,有两个,其中一个是正确的

    • 1 个回答
  • Marko Smith

    在 PyQt5 中停止进程

    • 1 个回答
  • Marko Smith

    我的脚本不工作

    • 1 个回答
  • Marko Smith

    在文本文件中写入和读取列表

    • 2 个回答
  • Marko Smith

    如何像屏幕截图中那样并排排列这些块?

    • 1 个回答
  • Marko Smith

    确定文本文件中每一行的字符数

    • 2 个回答
  • Marko Smith

    将接口对象传递给 JAVA 构造函数

    • 1 个回答
  • Marko Smith

    正确更新数据库中的数据

    • 1 个回答
  • Marko Smith

    Python解析不是css

    • 1 个回答
  • Martin Hope
    Alexandr_TT 2020年新年大赛! 2020-12-20 18:20:21 +0000 UTC
  • Martin Hope
    Alexandr_TT 圣诞树动画 2020-12-23 00:38:08 +0000 UTC
  • Martin Hope
    Air 究竟是什么标识了网站访问者? 2020-11-03 15:49:20 +0000 UTC
  • Martin Hope
    Qwertiy 号码显示 9223372036854775807 2020-07-11 18:16:49 +0000 UTC
  • Martin Hope
    user216109 如何为黑客设下陷阱,或充分击退攻击? 2020-05-10 02:22:52 +0000 UTC
  • Martin Hope
    Qwertiy 并变成3个无穷大 2020-11-06 07:15:57 +0000 UTC
  • Martin Hope
    koks_rs 什么是样板代码? 2020-10-27 15:43:19 +0000 UTC
  • Martin Hope
    Sirop4ik 向 git 提交发布的正确方法是什么? 2020-10-05 00:02:00 +0000 UTC
  • Martin Hope
    faoxis 为什么在这么多示例中函数都称为 foo? 2020-08-15 04:42:49 +0000 UTC
  • Martin Hope
    Pavel Mayorov 如何从事件或回调函数中返回值?或者至少等他们完成。 2020-08-11 16:49:28 +0000 UTC

热门标签

javascript python java php c# c++ html android jquery mysql

Explore

  • 主页
  • 问题
    • 热门问题
    • 最新问题
  • 标签
  • 帮助

Footer

RError.com

关于我们

  • 关于我们
  • 联系我们

Legal Stuff

  • Privacy Policy

帮助

© 2023 RError.com All Rights Reserve   沪ICP备12040472号-5