Javascript - HTML Canvas 上的 Gecko 边界半径自适应(CSS 边界半径)
Javascript - Gecko border radius adaptation on HTML Canvas (CSS border-radius)
我正在尝试弄清楚如何将“border-radius”css 属性 的行为重现为 HTML canvas.
所以我已经在 Javascript 中做了一些事情,以便使用特定的半径(对于每个角)计算给定形状的正确边界。
如果需要的话,这里是上一个问题:
我已经接近浏览器适配了,但还有一个问题,似乎是最后一个,也是最难的部分!
举个例子给大家说明问题
采用 100px
宽度和 100px
高度的形状。现在将以下半径应用于每个角:
- 左上:37px
- 右上:100px
- 右下:1px
- 左下角:100px
因此 css 样式为 border-radius : 37px 100px 1px 100px
现在让我们考虑使用 下面的代码 以 canvas.
绘制此形状
通过使用函数 correctRadius(r, w, h)
以避免不良形状,每个角都将像这样计算:
=> {tl: 18.5, tr: 81.5, br: 0.5, bl: 81.5}
这是一个视觉效果:
You will be able to test it in the following snippet
如您所见,浏览器形状(绿色)与 canvas 形状(棕色 + 'pink' 由于不透明度)重叠。为了检查坏角(粉红色),我在上面加了一些不透明度。
棕色的形状不适合绿色的形状,左上角离开底部,左下角和右上角角 不适合绿色形状。
我已经尝试修复该问题但未成功,并且还查看了此文件中 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>
Found the solution !
我们只需要计算一个 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>
我正在尝试弄清楚如何将“border-radius”css 属性 的行为重现为 HTML canvas.
所以我已经在 Javascript 中做了一些事情,以便使用特定的半径(对于每个角)计算给定形状的正确边界。
如果需要的话,这里是上一个问题:
我已经接近浏览器适配了,但还有一个问题,似乎是最后一个,也是最难的部分!
举个例子给大家说明问题
采用 100px
宽度和 100px
高度的形状。现在将以下半径应用于每个角:
- 左上:37px
- 右上:100px
- 右下:1px
- 左下角:100px
因此 css 样式为 border-radius : 37px 100px 1px 100px
现在让我们考虑使用 下面的代码 以 canvas.
绘制此形状通过使用函数 correctRadius(r, w, h)
以避免不良形状,每个角都将像这样计算:
=> {tl: 18.5, tr: 81.5, br: 0.5, bl: 81.5}
这是一个视觉效果:
You will be able to test it in the following snippet
如您所见,浏览器形状(绿色)与 canvas 形状(棕色 + 'pink' 由于不透明度)重叠。为了检查坏角(粉红色),我在上面加了一些不透明度。
棕色的形状不适合绿色的形状,左上角离开底部,左下角和右上角角 不适合绿色形状。
我已经尝试修复该问题但未成功,并且还查看了此文件中 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>
Found the solution !
我们只需要计算一个 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>