简体   繁体   English

使用canvas API复制CSS边界半径

[英]replicate CSS border-radius with canvas API

I want to reproduce the following CSS shape into a canvas. 我想将以下CSS形状复制到画布中。 It has the following properties : 它具有以下属性:

width: 325px;
height: 200px;
background: green;
border-radius: 60px 110px / 100px 80px;

渲染的形状

I already have a function to make rounded rectangles, but not so deformed... See associated stack post : https://stackoverflow.com/a/48491607/9264003 我已经有一个制作圆角矩形的功能,但没有那么变形……请参阅相关的堆栈文章: https : //stackoverflow.com/a/48491607/9264003

I've tried bezierCurveTo() & arcTo() functions, but with no success. 我已经尝试过bezierCurveTo()arcTo()函数,但是没有成功。

I think we have to compute an oval for each corner: Top-left, Top-Right, Bottom-Right, Bottom-Left, but I am not sure at all... 我认为我们必须为每个角计算一个椭圆:左上角,右上角,右下角,左下角,但我不确定...

If anyone were to reproduce this shape, or give me any tip or formula in order to compute this that would be a very good start ! 如果有人要重现此形状,或者给我任何提示或公式以进行计算,那将是一个很好的开始!

There is actually an ellipse method for the 2DContext API. 实际上,2DContext API有一个ellipse方法。 Its browser support is not awesome, but it can be polyfilled . 它对浏览器的支持并不出色,但可以将其填充

Using this method simplifies a lot the operation, since we can just use it to draw a quarter of ellipse at every corners, but it doesn't solve the whole problem... 使用此方法简化了很多操作,因为我们只能使用它在每个角上画出四分之一的椭圆,但是并不能解决整个问题...

You will still have to parse the CSSString to know how to draw your ellipses, for this, I use a simple dummy div + getComputedStyle. 您仍然必须解析CSSString才能知道如何绘制椭圆,为此,我使用了一个简单的虚拟div + getComputedStyle。
According to specs, if one of the two border-XXX-XXX-radius computed values is 0px , then we should draw a squared corner. 根据规范,如果两个border-XXX-XXX-radius计算值之一为0px ,则应绘制一个平方角。
You will also have to account for overlapping corners rule , and for this, I borrowed niklasvh's html2canvas implementation of the CSSWG algo. 您还必须考虑重叠角规则 ,为此,我借用了niklasvh的CSSWG算法的html2canvas实现

Here is my attempt to create such a Border-radius function to canvas, but I didn't made extensive tests, so it might fail. 这是我尝试在画布上创建这样的Border-radius函数的尝试,但是我没有进行广泛的测试,因此它可能会失败。

Also note that it only accepts the shorthand border-radius CSS syntax, though it wuold be easy to tweak if you also want to pass long-hand ones. 还要注意,它只接受简写的border-radius CSS语法,尽管如果您还想传递长手的语法,则很容易进行调整。

 var w = c.width = 325, h = c.height = 200, ctx = c.getContext('2d'); ctx.fillStyle = 'blue'; inp.onchange = function() { blurb.style.borderRadius = this.value; ctx.clearRect(0, 0, w, h); drawBorderRadius(ctx, this.value, w, h); ctx.fill(); }; inp.onchange(); function drawBorderRadius(ctx, CSSRule, w, h) { var radii = parseBorderRadiusRules(CSSRule); fixOverlappingCorners(radii); ctx.beginPath(); var x, y, h_, v_; // top-left corner if (hasZero(radii.topleft)) ctx.moveTo(0, 0); else { x = radii.topleft[0]; y = radii.topleft[1]; ctx.ellipse(x, y, x, y, 0, Math.PI, Math.PI * 1.5); } // top-right corner if (hasZero(radii.topright)) ctx.lineTo(w, 0); else { x = radii.topright[0]; y = radii.topright[1]; ctx.ellipse(w - radii.topright[0], y, x, y, 0, -Math.PI / 2, 0); } //bottom-right corner if (hasZero(radii.bottomright)) ctx.lineTo(w, h); else { x = radii.bottomright[0]; y = radii.bottomright[1]; ctx.ellipse(w - x, h - y, x, y, 0, 0, Math.PI / 2); } //bottom-left corner if (hasZero(radii.bottomleft)) ctx.lineTo(0, h); else { x = radii.bottomleft[0]; y = radii.bottomleft[1]; ctx.ellipse(x, h - y, x, y, 0, Math.PI / 2, Math.PI); } // we need to check if one value is zero in order to draw a squared corner in such case function hasZero(corner) { return !Array.isArray(corner) || corner.indexOf(0) > -1 || corner.indexOf(NaN) > -1; } // returns a dictionnary of four corners [horizontal, vertical] values as px function parseBorderRadiusRules(CSSstring) { var elem = document.createElement('div'); elem.style.borderRadius = CSSstring; elem.style.width = w; elem.style.height = h; elem.style.position = 'absolute'; elem.zIndex = -999; document.body.appendChild(elem); var computed = getComputedStyle(elem); var radii = { topleft: cleanRule(computed['border-top-left-radius']), topright: cleanRule(computed['border-top-right-radius']), bottomright: cleanRule(computed['border-bottom-right-radius']), bottomleft: cleanRule(computed['border-bottom-left-radius']) }; document.body.removeChild(elem); return radii; function cleanRule(str) { var xy = str.split(' '); if (xy.length === 1) { xy[1] = xy[0]; } return xy.map(toPx); } function toPx(str, index) { var val = parseFloat(str); if (str.indexOf('%') > -1) { return percentageToPx(val, !index ? w : h); } return val; } function percentageToPx(percent, length) { return length * (percent / 100); } } // borrowed from https://github.com/niklasvh/html2canvas/blob/8788a9f458f538c004a626c5ce7ee24b53e48c1c/src/Bounds.js#L200 // https://github.com/niklasvh/html2canvas/blob/master/LICENSE function fixOverlappingCorners(radii) { var factors = [ w / (radii.topleft[0] + radii.topright[0]), w / (radii.bottomleft[0] + radii.bottomright[0]), h / (radii.topleft[1] + radii.bottomleft[1]), h / (radii.topright[1] + radii.bottomright[1]) ], minFactor = Math.min.apply(null, factors); if (minFactor <= 1) { for (var key in radii) { radii[key] = radii[key].map(scale); } } function scale(value) { return value * minFactor; } } } 
 #blurb { width: 325px; height: 200px; background: green; } input:checked+.cont>#blurb { opacity: 0.5; position: absolute; background: red; } .cont { position: relative; } 
 <input id="inp" value="60px 110px / 100px 80px"> <label>overlap</label><input type="checkbox"> <div class="cont"> <div id="blurb"></div> <canvas id="c"></canvas> </div> 

Here it is as an SVG, using arc segments: 这是使用弧段的SVG:

<path d="M 60,0 225,0 A 100,80 0 0 1 325,80 L 325,90 A 60,110 0 0 1 265,200 L 100,200 A 100,80 0 0 1 0,120 L 0,110 A 60,110 0 0 1 60,0 Z" />

Screenshot in Inkscape: Inkscape中的屏幕截图:

截图

Depending what you're trying to do, it may be simplest just to use SVG and forget canvas. 根据您要执行的操作,仅使用SVG而忘记画布可能是最简单的。

But if you really need this to be on a canvas, you can build the same thing with arcs but it's trickier because arcTo does only circular arcs, not elliptical ones. 但是,如果确实需要将其放在画布上,则可以使用圆弧构建相同的东西,但这比较棘手,因为arcTo仅执行圆弧,而不执行椭圆弧。

One way to get around that is to change the scaling of the drawing context before drawing each arc. 解决该问题的一种方法是在绘制每个弧之前更改绘制上下文的缩放比例。

Here's an example: 这是一个例子:

 const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); function ellipticalArcTo(x1, y1, x2, y2, rx, ry) { ctx.save(); const xScale = 1; const yScale = ry / rx; ctx.scale(xScale, yScale); ctx.arcTo(x1 / xScale, y1 / yScale, x2 / xScale, y2 / yScale, rx); ctx.restore(); } ctx.fillStyle = 'green'; ctx.beginPath(); ctx.moveTo(60, 0); ctx.lineTo(225, 0); ellipticalArcTo(325, 0, 325, 80, 100, 80); ctx.lineTo(325, 90); ellipticalArcTo(325, 200, 215, 200, 60, 110); ctx.lineTo(100, 200); ellipticalArcTo(0, 200, 0, 120, 100, 80); ctx.lineTo(0, 110); ellipticalArcTo(0, 0, 60, 0, 60, 110); ctx.fill(); 
 <canvas id="canvas" width="400" height="300"></canvas> 

Note that I haven't tested this with any rotated arcs. 请注意,我尚未使用任何旋转的弧进行测试。 For all I know, it's broken when the major axes are not horizontal and vertical. 据我所知,当主轴不是水平和垂直时,它就坏了。 But you don't need this to simulate border-radius of a rectangle. 但是您不需要使用它来模拟矩形的边界半径。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM