Gecko - CSS布局边界半径 - Javascript转换

时间:2018-01-26 17:11:17

标签: javascript html c++ css canvas

我试图获取确切的公式,以便在画布中计算CSS border-radius属性。我已经尝试过在javascript中有一个例子(见下文),但没有成功。

似乎浏览器仍在添加一些调整以调整边界。而且我无法识别它们。所以我已经检查了Gecko布局引擎的来源,但我不确定在哪里可以找到这个公式(在源代码中)。

它可能在layout/painting/nsCSSRenderingBorders.cpp,但仍有很多代码和它的C ++(我没有使用该语言的技能)

请参阅Gecko存储库https://github.com/mozilla/gecko-dev

所以,如果有人能帮助我实现这种改编,或者给我一些代码块,这些代码块正在计算边界半径"弧线,方向?" (在壁虎中)我能够做到这一点。

Javascript / HTML代码段(当前,接近良好的适应性)

(RED SHAPE =画布形状,GREEN SHAPE = HTML形状)

我使用此功能绘制形状:ctx.constructor.prototype.fillRoundedRect

这个功能是为了接近浏览器改编:correctRadius

正如您将看到的,当TopLEFT,TopRight和BottomLEFT滑块达到最大值时,我得到此结果。浏览器(绿色)渲染得很完美,我的很糟糕(红色)。

请参阅下面的代码段

bad result js border radius css



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

function correctRadius(r, w, h) {
  if (r.tl + r.tr > w) {
    r.tl -= (r.tl + r.tr - w) / 2;
    r.tr = w - r.tl;
  }
  if (r.bl + r.br > w) {
    r.br -= (r.br + r.bl - w) / 2;
    r.bl = w - r.br;
  }
  if (r.tl + r.bl > h) {
    r.tl -= (r.tl + r.bl - h) / 2;
    r.bl = h - r.tl;
  }
  if (r.tr + r.br > h) {
    r.tr -= (r.tr + r.br - h) / 2;
    r.br = h - r.tr;
  }
 }

//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 off = document.getElementById('off');

function test() {
ctx.clearRect(0, 0, 600, 500);


/* 1.Top left */
/* 2. Top right */
/* 3. Bottom right  */
/* 4. Bottom left */
var borders = [tl.value, tr.value, br.value, bl.value].join('px ') + 'px';

copy.style.borderRadius = borders;
var copyRad = borders.replace(/px/g, '').split(' ').map(function (a) {
	return parseInt(a)
});

var rad = {
	tl: copyRad[0],
	tr: copyRad[1],
	br: copyRad[2],
	bl: copyRad[3]
};
var o = +off.value;
ctx.fillRoundedRect(15 + o, 15 + o, 100, 100, rad);
}

tl.oninput = test;
tr.oninput = test;
bl.oninput = test;
br.oninput = test;
off.oninput = test;
test();

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
        html, body {
            margin: 0;
            padding: 0;
        }
    </style>
</head>
<body>


<div style="display:inline-block; position: absolute;
left:120px;top:120px; width: 100px; height: 100px; background:green;

border-radius: 10px 5px 10px 20px;" id="copy">

</div>

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

</canvas>


<div style="top: 300px; position:absolute; z-index: 1;">
    <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/>
    <label>
        Offset
        <input type="range" min="1" max="200" value="0" class="slider" id="off"></label><br/>
</div>

</body>
</html>
&#13;
&#13;
&#13;

1 个答案:

答案 0 :(得分:1)

问题在于您的correctRadius功能。正如您所看到的,您不止一次重新计算值,这是不正确的。

让我们以TopLeft,TopRight和BottomLeft设置为最大值(100px)为例:

1)您将考虑第一个条件,您将更新您的值:

TopLeft = 50px | TopRight = 50px(这些是正确的值)

2)现在你将考虑第三个条件,因为你有TL + TB(100px + 50px)&gt; h(100px),你将更新这样的值:

TopLeft = 25px | BottomLeft 75px(这些错误的值,正确的值应为50px)

因此,您不必单独计算值,您应该考虑所有这些值,并根据初始值对每一方进行计算。< / p>

这个想法是考虑两个相邻边之间差异的最大值。让我们考虑上面的相同示例并制作3个不同的值,如下所示:

TL = 100px | TR = 90px | BL = 100px | BR = 0px

TL与TR和BL相邻:

  • 考虑到TR,我们将190px > 100px,因此TL = 45px
  • 考虑到BL,我们将200px > 100px,此TL = 50px

因此我们应该考虑 50px 的最大值。我们对其他方面也这样做。

以下是完整代码:

&#13;
&#13;
// Ctx
var ctx = document.getElementById("rounded-rect").getContext("2d");

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 off = document.getElementById('off');

function test() {
ctx.clearRect(0, 0, 600, 500);


/* 1.Top left */
/* 2. Top right */
/* 3. Bottom right  */
/* 4. Bottom left */
var borders = [tl.value, tr.value, br.value, bl.value].join('px ') + 'px';

copy.style.borderRadius = borders;
var copyRad = borders.replace(/px/g, '').split(' ').map(function (a) {
	return parseInt(a)
});

var rad = {
	tl: copyRad[0],
	tr: copyRad[1],
	br: copyRad[2],
	bl: copyRad[3]
};
var o = +off.value;
ctx.fillRoundedRect(15 + o, 15 + o, 100, 100, rad);
}

tl.oninput = test;
tr.oninput = test;
bl.oninput = test;
br.oninput = test;
off.oninput = test;
test();
&#13;
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style>
        html, body {
            margin: 0;
            padding: 0;
        }
    </style>
</head>
<body>


<div style="display:inline-block; position: absolute;
left:120px;top:120px; width: 100px; height: 100px; background:green;

border-radius: 10px 5px 10px 20px;" id="copy">

</div>

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

</canvas>


<div style="top: 300px; position:absolute; z-index: 1;">
    <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/>
    <label>
        Offset
        <input type="range" min="1" max="200" value="0" class="slider" id="off"></label><br/>
</div>

</body>
</html>
&#13;
&#13;
&#13;