簡體   English   中英

使用canvas API復制CSS邊界半徑

[英]replicate CSS border-radius with canvas API

我想將以下CSS形狀復制到畫布中。 它具有以下屬性:

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

渲染的形狀

我已經有一個制作圓角矩形的功能,但沒有那么變形……請參閱相關的堆棧文章: https : //stackoverflow.com/a/48491607/9264003

我已經嘗試過bezierCurveTo()arcTo()函數,但是沒有成功。

我認為我們必須為每個角計算一個橢圓:左上角,右上角,右下角,左下角,但我不確定...

如果有人要重現此形狀,或者給我任何提示或公式以進行計算,那將是一個很好的開始!

實際上,2DContext API有一個ellipse方法。 它對瀏覽器的支持並不出色,但可以將其填充

使用此方法簡化了很多操作,因為我們只能使用它在每個角上畫出四分之一的橢圓,但是並不能解決整個問題...

您仍然必須解析CSSString才能知道如何繪制橢圓,為此,我使用了一個簡單的虛擬div + getComputedStyle。
根據規范,如果兩個border-XXX-XXX-radius計算值之一為0px ,則應繪制一個平方角。
您還必須考慮重疊角規則 ,為此,我借用了niklasvh的CSSWG算法的html2canvas實現

這是我嘗試在畫布上創建這樣的Border-radius函數的嘗試,但是我沒有進行廣泛的測試,因此它可能會失敗。

還要注意,它只接受簡寫的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> 

這是使用弧段的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" />

Inkscape中的屏幕截圖:

截圖

根據您要執行的操作,僅使用SVG而忘記畫布可能是最簡單的。

但是,如果確實需要將其放在畫布上,則可以使用圓弧構建相同的東西,但這比較棘手,因為arcTo僅執行圓弧,而不執行橢圓弧。

解決該問題的一種方法是在繪制每個弧之前更改繪制上下文的縮放比例。

這是一個例子:

 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> 

請注意,我尚未使用任何旋轉的弧進行測試。 據我所知,當主軸不是水平和垂直時,它就壞了。 但是您不需要使用它來模擬矩形的邊界半徑。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM