Skip to content

Commit c66e28d

Browse files
LeaVeroufantasai
andcommitted
[css-backgrounds-3] New radius expansion demo
- Implements Elika's algorithm correctly - Allows live editing of testcases Co-Authored-By: fantasai <725717+fantasai@users.noreply.github.com>
1 parent 4ce73db commit c66e28d

File tree

1 file changed

+284
-0
lines changed

1 file changed

+284
-0
lines changed
Lines changed: 284 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,284 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
7+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
8+
<title>Radius expansion algorithms</title>
9+
<style>
10+
body {
11+
display: flex;
12+
height: 100vh;
13+
margin: 0;
14+
}
15+
#output {
16+
flex: 1;
17+
border-left: 1px solid;
18+
margin-left: 1em;
19+
overflow: auto;
20+
}
21+
#output, form {
22+
padding: 1em;
23+
}
24+
hr {
25+
border: none;
26+
border-top: 1px dotted;
27+
}
28+
label {
29+
display: flex;
30+
width: max-content;
31+
margin: .5em 0;
32+
}
33+
34+
article {
35+
padding: 1em;
36+
}
37+
38+
input:not([type]) {
39+
font: 100%/1.5 Consolas, Monaco, monospace;
40+
width: 80ch;
41+
margin-bottom: .5em;
42+
}
43+
</style>
44+
45+
</head>
46+
<body>
47+
48+
<form>
49+
<label>
50+
<input type="radio" name="algorithm" value="do-not-polyfill" checked>
51+
Do not polyfill
52+
</label>
53+
<label>
54+
<input type="radio" name="algorithm" value="increase-by-spread">
55+
Increase radius by spread
56+
</label>
57+
<label>
58+
<input type="radio" name="algorithm" value="old-spec">
59+
Old spec (discontinuous)
60+
</label>
61+
<label>
62+
<input type="radio" name="algorithm" value="current-spec">
63+
Current spec
64+
</label>
65+
<label>
66+
<input type="radio" name="algorithm" value="percentage-same-axis">
67+
Percentage of same axis
68+
</label>
69+
<label>
70+
<input type="radio" name="algorithm" value="elika">
71+
Elika’s Interpolation based on rounded/straight ratio
72+
</label>
73+
</form>
74+
<output id="output"></output>
75+
<script>
76+
const {algorithm} = document.forms[0].elements;
77+
const testCases = [
78+
{width: 50, height: 50, spread: 50, borderRadius: "0px"},
79+
{width: 50, height: 50, spread: 50, borderRadius: "1px"},
80+
{width: 10, height: 10, spread: 70, borderRadius: "100%"},
81+
{width: 200, height: 40, spread: 50, borderRadius: "100px / 20px"},
82+
{width: 200, height: 40, spread: 50, borderRadius: "20px / 4px"},
83+
{width: 500, height: 50, spread: 30, borderRadius: "15px"},
84+
{width: 500, height: 50, spread: 30, borderRadius: "25px"},
85+
{width: 500, height: 50, spread: 30, borderRadius: "1px 1px 49px 49px"},
86+
{width: 500, height: 50, spread: 30, borderRadius: "50%"},
87+
{width: 500, height: 50, spread: 30, borderRadius: "50% 50% 1px 50%"},
88+
];
89+
90+
function show({incremental = false} = {}) {
91+
for (let i = 0, testCase; testCase = testCases[i]; i++) {
92+
let container = output.children[i];
93+
94+
if (!container) {
95+
container = document.createElement("article");
96+
container.id = `testcase${i}`;
97+
output.appendChild(container);
98+
}
99+
100+
if (!incremental) {
101+
let input = document.createElement("input");
102+
input.value = JSON.stringify(testCase);
103+
input.oninput = () => {
104+
try {
105+
testCases[i] = JSON.parse(input.value);
106+
show({incremental: true});
107+
}
108+
catch (e) {}
109+
};
110+
111+
container.append(input);
112+
}
113+
114+
Array.from(container.querySelectorAll("div")).forEach(e => e.remove());
115+
116+
const inner = document.createElement("div");
117+
inner.style.width = testCase.width + "px";
118+
inner.style.height = testCase.height + "px";
119+
inner.style.borderRadius = testCase.borderRadius;
120+
inner.style.backgroundColor = "#fff";
121+
122+
const outer = document.createElement("div");
123+
outer.appendChild(inner);
124+
125+
if (algorithm.value === "do-not-polyfill") {
126+
inner.style.boxShadow = `0 0 0 ${testCase.spread}px #000`;
127+
outer.style.padding = testCase.spread + "px";
128+
container.appendChild(outer);
129+
}
130+
else {
131+
outer.style.backgroundColor = "#000";
132+
outer.style.borderStyle = "solid";
133+
outer.style.borderWidth = testCase.spread + "px";
134+
outer.style.width = "max-content";
135+
container.appendChild(outer);
136+
outer.style.borderRadius = resolve(testCase, inner);
137+
}
138+
}
139+
}
140+
141+
function parseCorner(value, testCase) {
142+
const raw = value.split(" ");
143+
if (raw.length === 1) {
144+
raw[1] = raw[0];
145+
}
146+
return [
147+
parseLength(raw[0], testCase.width),
148+
parseLength(raw[1], testCase.height),
149+
];
150+
}
151+
152+
function parseLength(value, percentageBasis) {
153+
if (value.endsWith("%")) {
154+
return parseFloat(value) * percentageBasis / 100;
155+
}
156+
157+
return parseFloat(value);
158+
}
159+
160+
function resolve(testCase, box) {
161+
const cs = getComputedStyle(box);
162+
163+
// Corners to array[2] of radii
164+
const radii = {
165+
topLeft: parseCorner(cs.borderTopLeftRadius, testCase),
166+
topRight: parseCorner(cs.borderTopRightRadius, testCase),
167+
bottomLeft: parseCorner(cs.borderBottomLeftRadius, testCase),
168+
bottomRight: parseCorner(cs.borderBottomRightRadius, testCase),
169+
};
170+
171+
// Normalize radii that add up to > 100%
172+
const f = Math.min(
173+
testCase.width / (radii.topLeft[0] + radii.topRight[0]),
174+
testCase.width / (radii.bottomLeft[0] + radii.bottomRight[0]),
175+
testCase.height / (radii.topLeft[1] + radii.bottomLeft[1]),
176+
testCase.height / (radii.topRight[1] + radii.bottomRight[1])
177+
);
178+
if (f < 1) {
179+
for (let corner in radii) {
180+
radii[corner] = radii[corner].map(v => v * f);
181+
}
182+
}
183+
184+
let r = {
185+
topLeft: radii.topLeft,
186+
topRight: radii.topRight,
187+
bottomLeft: radii.bottomLeft,
188+
bottomRight: radii.bottomRight,
189+
};
190+
const algorithm = document.forms[0].elements.algorithm.value;
191+
192+
let {width, height} = testCase;
193+
let spreadWidth = width + testCase.spread * 2;
194+
let spreadHeight = height + testCase.spread * 2;
195+
196+
let percentageSameAxis = {};
197+
198+
for (let corner in r) {
199+
let c = r[corner];
200+
let [rx, ry] = c;
201+
202+
let px = rx / width;
203+
let py = ry / height;
204+
205+
percentageSameAxis[corner] = [px * spreadWidth, py * spreadHeight];
206+
}
207+
208+
let currentSpec = {};
209+
210+
for (let corner in r) {
211+
currentSpec[corner] = r[corner].map(value => {
212+
if (value >= testCase.spread) {
213+
return value + testCase.spread;
214+
}
215+
let r = value / testCase.spread;
216+
return value + testCase.spread * (1 + (r - 1)**3);
217+
});
218+
}
219+
220+
if (algorithm === "increase-by-spread") {
221+
for (let corner in r) {
222+
r[corner] = r[corner].map(v => v + testCase.spread);
223+
}
224+
}
225+
else if (algorithm === "old-spec") {
226+
for (let corner in r) {
227+
let c = r[corner];
228+
r[corner] = c[0] + c[1] === 0 ? [0, 0] : [c[0] + testCase.spread, c[1] + testCase.spread];
229+
}
230+
}
231+
else if (algorithm === "percentage-same-axis") {
232+
r = percentageSameAxis;
233+
}
234+
else if (algorithm === "current-spec") {
235+
r = currentSpec;
236+
}
237+
else if (algorithm === "elika") {
238+
let {width, height} = testCase;
239+
let spreadWidth = width + testCase.spread * 2;
240+
let spreadHeight = height + testCase.spread * 2;
241+
242+
let straights = {
243+
top: width - r.topLeft[0] - r.topRight[0],
244+
bottom: width - r.bottomLeft[0] - r.bottomRight[0],
245+
left: height - r.topLeft[1] - r.bottomLeft[1],
246+
right: height - r.topRight[1] - r.bottomRight[1],
247+
}
248+
249+
function getStraightSegment(corner, axis) {
250+
/*
251+
Example straight segment returned:
252+
topLeft, 0 --> top
253+
topLeft, 1 --> left
254+
bottomRight, 0 --> bottom
255+
bottomRight, 1 --> right
256+
*/
257+
258+
let parts = corner.split(/(?=[A-Z])/).map(part => part.toLowerCase());
259+
return straights[parts[axis]];
260+
}
261+
262+
for (let corner in r) {
263+
r[corner] = r[corner].map((value, axis) => {
264+
let straight = getStraightSegment(corner, axis);
265+
let ratio = straight / value;
266+
ratio = Math.min(ratio, 1);
267+
let ret = ratio * currentSpec[corner][axis] + (1 - ratio) * percentageSameAxis[corner][axis];
268+
269+
return Math.min(ret, straight + value + testCase.spread);
270+
});
271+
272+
}
273+
}
274+
275+
return `${r.topLeft[0]}px ${r.topRight[0]}px ${r.bottomRight[0]}px ${r.bottomLeft[0]}px / ${r.topLeft[1]}px ${r.topRight[1]}px ${r.bottomRight[1]}px ${r.bottomLeft[1]}px`;
276+
}
277+
278+
show();
279+
document.querySelector("form").addEventListener("change", evt => show({incremental: true}));
280+
281+
</script>
282+
283+
</body>
284+
</html>

0 commit comments

Comments
 (0)