Javascript - Adapting the Gecko border radius on HTML canvas (CSS border border)

I am trying to figure out how to reproduce the behavior of the css property "border-radius" in an HTML canvas.

So, I already did something in Javascript to calculate the correct borders of a given shape using a specific radius (for each corner).

Here is the previous question, if necessary: Gecko - border of the CSS layout frame - Javascript transform

I managed to get closer to the adaptation of the browser, but there is still a problem, and it seems that this is the last and difficult part!

Here is an example to explain the problem to you.

Take the shape of the width 100pxand 100px. Now apply the following radius to each corner:

  • Top left: 37px
  • Top right: 100px
  • Bottom right: 1px
  • Bottom left: 100px

, css border-radius : 37px 100px 1px 100px

, .

correctRadius(r, w, h), , :

= > {tl: 18.5, tr: 81.5, br: 0.5, bl: 81.5}

:

calc problem css border radius canvas

, () ( + "" - ). , ().

, , t .

, , Gecko (https://github.com/mozilla/gecko-dev) : layout/painting/nsCSSRenderingBorders.cpp, + ++


- ,

// Ctx
var ctx = document.getElementById("rounded-rect").getContext("2d");
ctx.translate(0, 0);

function correctRadius(r, w, h) {
  var tl = r.tl;
  var tr = r.tr;
  var br = r.br;
  var bl = r.bl;


  r.tl -= Math.max(Math.max((tl + tr - w) / 2, 0),
    Math.max((tl + bl - h) / 2, 0));

  r.tr -= Math.max(Math.max((tr + tl - w) / 2, 0),
    Math.max((tr + br - h) / 2, 0));

  r.br -= Math.max(Math.max((br + bl - w) / 2, 0),
    Math.max((br + tr - h) / 2, 0));

  r.bl -= Math.max(Math.max((bl + br - w) / 2, 0),
    Math.max((bl + tl - h) / 2, 0));


}


//Round rect func
ctx.constructor.prototype.fillRoundedRect =
  function(xx, yy, ww, hh, rad, fill, stroke) {
    correctRadius(rad, ww, hh);
    if (typeof(rad) === "undefined") rad = 5;
    this.beginPath();
    this.moveTo(xx, yy);
    this.arcTo(xx + ww, yy, xx + ww, yy + hh, rad.tr);
    this.arcTo(xx + ww, yy + hh, xx, yy + hh, rad.br);
    this.arcTo(xx, yy + hh, xx, yy, rad.bl);
    this.arcTo(xx, yy, xx + ww, yy, rad.tl);
    if (stroke) this.stroke(); // Default to no stroke
    if (fill || typeof(fill) === "undefined") this.fill(); // Default to fill
  };

ctx.fillStyle = "red";
ctx.strokeStyle = "#ddf";

var copy = document.getElementById('copy');
var tl = document.getElementById('tl');
var tr = document.getElementById('tr');
var bl = document.getElementById('bl');
var br = document.getElementById('br');

var last = [];
setInterval(function() {


  /* 1.Top left */
  /* 2. Top right */
  /* 3. Bottom right  */
  /* 4. Bottom left */

  var bordersCSSProps = [
      "border-top-left-radius",
      "border-top-right-radius",
      "border-bottom-right-radius",
      "border-bottom-left-radius"
    ],
    elementBorders = [],
    elementStyle = getComputedStyle(copy);

  var changed = false;

  for (var i = 0; i < 4; ++i) {
    elementBorders[i] = elementStyle.getPropertyValue(bordersCSSProps[i]);
    if (elementBorders[i] !== last[i]) {
      changed = true;
      last[i] = elementBorders[i];
    }
  }

  if (changed) {

    var borders = [].concat(elementBorders).map(function(a) {
      return parseInt(a)
    });
    var rad = {
      tl: borders[0],
      tr: borders[1],
      br: borders[2],
      bl: borders[3]
    };

    ctx.clearRect(0, 0, 600, 500);


    ctx.fillRoundedRect(120, 120, 100, 100, rad);


  }
}, 1E3 / 60);


function elemBordersSet() {
  var borders = [tl.value, tr.value, br.value, bl.value].join('px ') + 'px';
  copy.style.borderRadius = borders;

}

tl.oninput = elemBordersSet;
tr.oninput = elemBordersSet;
bl.oninput = elemBordersSet;
br.oninput = elemBordersSet;
html,
body {
  margin: 0;
  padding: 0;
}
<div style="display:inline-block; position: absolute;
left:120px;top:120px; width: 100px; height: 100px; background:green;

border-radius:  100px 49px 1px 1px;" id="copy">

</div>

<canvas style="opacity:0.5; z-index:1; display: inline-block; position: absolute; left:0; top:0;" id="rounded-rect" width="600" height="500">

</canvas>


<div style="margin-top:250px; position:absolute; z-index:5">
  <label>
        Top left
        <input type="range" min="1" max="100" value="0" class="slider" id="tl"></label><br/>
  <label>
        Top right
        <input type="range" min="1" max="100" value="0" class="slider" id="tr"></label><br/>
  <label>
        Bottom left
        <input type="range" min="1" max="100" value="0" class="slider" id="bl"></label><br/>
  <label>
        Bottom right
        <input type="range" min="1" max="100" value="0" class="slider" id="br"></label><br/>
</div>
+4
1

!

a scaleRatio:

maxRadiusWidth (r.tl + r.tr, r.bl + r.br) maxRadiusHeight (r.tl + r.bl, r.tr + r.br)

, a widthRatio = (w / maxRadiusWidth) heightRatio = (h / maxRadiusHeight) (WIDTH HEIGHT)

: Math.min(Math.min(widthRatio, heightRatio), 1), , , 1.

, , !

r.tl = tl*scaleRatio;
r.tr = tr*scaleRatio;
r.br = br*scaleRatio;
r.bl = bl*scaleRatio;

. ;)

// Ctx
var ctx = document.getElementById("rounded-rect").getContext("2d");
ctx.translate(0, 0);

function correctRadius(r, w, h) {

        var
            maxRadiusWidth = Math.max(r.tl + r.tr, r.bl + r.br),
            maxRadiusHeight = Math.max(r.tl + r.bl, r.tr + r.br),
            widthRatio = w / maxRadiusWidth,
            heightRatio = h / maxRadiusHeight,
            scaleRatio = Math.min(Math.min(widthRatio, heightRatio), 1);


        for (var k in r)
            r[k] = r[k] * scaleRatio;

}


//Round rect func
ctx.constructor.prototype.fillRoundedRect =
    function(xx, yy, ww, hh, rad, fill, stroke) {
        correctRadius(rad, ww, hh);
        if (typeof(rad) === "undefined") rad = 5;
        this.beginPath();
        this.moveTo(xx, yy);
        this.arcTo(xx + ww, yy, xx + ww, yy + hh, rad.tr);
        this.arcTo(xx + ww, yy + hh, xx, yy + hh, rad.br);
        this.arcTo(xx, yy + hh, xx, yy, rad.bl);
        this.arcTo(xx, yy, xx + ww, yy, rad.tl);
        if (stroke) this.stroke(); // Default to no stroke
        if (fill || typeof(fill) === "undefined") this.fill(); // Default to fill
    };

ctx.fillStyle = "red";
ctx.strokeStyle = "#ddf";

var copy = document.getElementById('copy');
var tl = document.getElementById('tl');
var tr = document.getElementById('tr');
var bl = document.getElementById('bl');
var br = document.getElementById('br');

var last = [];
setInterval(function() {


    /* 1.Top left */
    /* 2. Top right */
    /* 3. Bottom right  */
    /* 4. Bottom left */

    var bordersCSSProps = [
            "border-top-left-radius",
            "border-top-right-radius",
            "border-bottom-right-radius",
            "border-bottom-left-radius"
        ],
        elementBorders = [],
        elementStyle = getComputedStyle(copy);

    var changed = false;

    for (var i = 0; i < 4; ++i) {
        elementBorders[i] = elementStyle.getPropertyValue(bordersCSSProps[i]);
        if (elementBorders[i] !== last[i]) {
            changed = true;
            last[i] = elementBorders[i];
        }
    }

    if (changed) {

        var borders = [].concat(elementBorders).map(function(a) {
            return parseInt(a)
        });
        var rad = {
            tl: borders[0],
            tr: borders[1],
            br: borders[2],
            bl: borders[3]
        };

        ctx.clearRect(0, 0, 600, 500);


        ctx.fillRoundedRect(120, 120, 100, 200, rad);


    }
}, 1E3 / 60);


function elemBordersSet() {
    var borders = [tl.value, tr.value, br.value, bl.value].join('px ') + 'px';
    copy.style.borderRadius = borders;

}

tl.oninput = elemBordersSet;
tr.oninput = elemBordersSet;
bl.oninput = elemBordersSet;
br.oninput = elemBordersSet;
<div style="display:inline-block; position: absolute;
left:120px;top:120px; width: 100px; height: 200px; background:green;

border-radius: 33px 71px 40px 100px;" id="copy">

</div>

<canvas style="z-index: 1;opacity:0.4;display: inline-block; position: absolute; left:0; top:0;" id="rounded-rect"
    width="600"
    height="500">

</canvas>


<div style="position: absolute; z-index: 5;margin-top: 330px;">
<label>
    Top left
    <input type="range" min="1" max="500" value="0" class="slider" id="tl"></label><br/>
<label>
    Top right
    <input type="range" min="1" max="500" value="0" class="slider" id="tr"></label><br/>
<label>
    Bottom left
    <input type="range" min="1" max="500" value="0" class="slider" id="bl"></label><br/>
<label>
    Bottom right
    <input type="range" min="1" max="500" value="0" class="slider" id="br"></label><br/>
</div>
0

Source: https://habr.com/ru/post/1692813/


All Articles