【问题标题】:Javascript - Gecko border radius adaptation on HTML Canvas (CSS border-radius)Javascript - HTML Canvas 上的 Gecko 边框半径适应(CSS 边框半径)
【发布时间】:2018-01-29 07:15:56
【问题描述】:

我试图弄清楚如何将“border-radius”css 属性的行为重现到 HTML 画布中。

所以我已经在 J​​avascript 中做了一些事情,以便使用特定半径(对于每个角)计算给定形状的正确边界。

如果需要,这里是上一个问题:Gecko - CSS layout border radius - Javascript conversion

我已经设法接近浏览器适配,但仍然存在问题,而且似乎是最后一个,也是最难的部分!

我们举个例子来说明问题

采用100px 宽度和100px 高度的形状。现在将以下半径应用于每个角:

  • 左上角:37px
  • 右上角:100px
  • 右下:1px
  • 左下:100px

所以这个css样式将是border-radius : 37px 100px 1px 100px

现在让我们考虑使用下面的代码在画布中绘制这个形状。

通过使用correctRadius(r, w, h)函数来避免错误的形状,每个角都将这样计算:

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

这是一个视觉效果:

你将可以在下面的sn-p中测试它

如您所见,浏览器形状(绿色)与画布形状(棕色 + '粉红色'由于不透明)重叠。为了检查坏角(粉红色),我在上面加了一些不透明度。

棕色形状不适合绿色形状,左上角超出底座,左下角和右上角不' t 适合绿色形状。

我已经尝试修复该问题但没有成功,并且还在此文件中查看了 Gecko 布局引擎 (https://github.com/mozilla/gecko-dev) 的来源:layout/painting/nsCSSRenderingBorders.cpp,但没有找到任何东西 + 我没有技能在 C++ 中


如果有人可以帮助我解决这个问题,或者给我一些建议,我将能够解决这个问题并让边界正常工作

// 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>

【问题讨论】:

  • 这似乎比我在上一个问题中所怀疑的要复杂 :) 我想我们必须进行一些舍入,我们需要应用一些地板/天花板,但不确定如何准确
  • 是的:/仍在努力! :)
  • 正在努力寻找解决方案...有人有提示或开始工作模式吗?
  • 是的,一点都不容易,我昨天尝试了很多东西,没有任何线索:如果你什么都没得到,你可以考虑给它加个赏金
  • 我正在尝试以某种比例来做到这一点。但还是没有头绪。我认为我不能这样做,因为我的(新)声誉低:S

标签: javascript css math canvas gecko


【解决方案1】:

找到了解决办法!

我们只需要计算一个scaleRatio

首先,我们得到maxRadiusWidth (r.tl + r.tr, r.bl + r.br) & maxRadiusHeight (r.tl + r.bl, r.tr + r.br)

然后一个 widthRatio = (w / maxRadiusWidth) & heightRatio = (h / maxRadiusHeight) 与形状的大小(宽度和高度)

然后我们取这两个变量中的较低者:Math.min(Math.min(widthRatio, heightRatio), 1),以便不变形&我们确保 比率低于 1。

最后,我们只需要将每个角乘以这个比率,就可以得到正确的尺寸!

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

见下面的 sn-p ;)

// 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>

【讨论】:

    猜你喜欢
    • 2018-07-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-07-02
    • 1970-01-01
    • 2021-11-21
    • 2018-07-11
    • 2012-06-17
    相关资源
    最近更新 更多