|
| 1 | +<!DOCTYPE html> |
| 2 | +<style> |
| 3 | +body { |
| 4 | + display: flex; |
| 5 | + height: 100vh; |
| 6 | + margin: 0; |
| 7 | +} |
| 8 | +#output { |
| 9 | + flex: 1; |
| 10 | + border-left: 1px solid; |
| 11 | + margin-left: 1em; |
| 12 | + overflow: auto; |
| 13 | +} |
| 14 | +#output, form { |
| 15 | + padding: 1em; |
| 16 | +} |
| 17 | +hr { |
| 18 | + border: none; |
| 19 | + border-top: 1px dotted; |
| 20 | +} |
| 21 | +label { |
| 22 | + display: flex; |
| 23 | + width: max-content; |
| 24 | + margin: .5em 0; |
| 25 | +} |
| 26 | +</style> |
| 27 | +<form> |
| 28 | + <label> |
| 29 | + <input type="radio" name="algorithm" value="do-not-polyfill" checked> |
| 30 | + Do not polyfill |
| 31 | + </label> |
| 32 | + <label> |
| 33 | + <input type="radio" name="algorithm" value="increase-by-spread"> |
| 34 | + Increase radius by spread |
| 35 | + </label> |
| 36 | + <label> |
| 37 | + <input type="radio" name="algorithm" value="current-spec"> |
| 38 | + Current spec |
| 39 | + </label> |
| 40 | + <label> |
| 41 | + <input type="radio" name="algorithm" value="percentage-min-axis"> |
| 42 | + Percentage of min axis |
| 43 | + </label> |
| 44 | + <label> |
| 45 | + <input type="radio" name="algorithm" value="percentage-max-axis"> |
| 46 | + Percentage of max axis |
| 47 | + </label> |
| 48 | + <label> |
| 49 | + <input type="radio" name="algorithm" value="percentage-same-axis"> |
| 50 | + Percentage of same axis |
| 51 | + </label> |
| 52 | + <label> |
| 53 | + <input type="radio" name="algorithm" value="percentage-same-axis-ratio"> |
| 54 | + Percentage of same axis,<br>ceiling to keep ratio if possible |
| 55 | + </label> |
| 56 | +</form> |
| 57 | +<div id="output"></div> |
| 58 | +<script> |
| 59 | +const output = document.getElementById("output"); |
| 60 | +const {algorithm} = document.forms[0].elements; |
| 61 | +const testCases = [ |
| 62 | + {width: 50, height: 50, spread: 50, borderRadius: "0px"}, |
| 63 | + {width: 50, height: 50, spread: 50, borderRadius: "1px"}, |
| 64 | + {width: 10, height: 10, spread: 70, borderRadius: "100%"}, |
| 65 | + {width: 200, height: 40, spread: 50, borderRadius: "100px / 20px"}, |
| 66 | + {width: 200, height: 40, spread: 50, borderRadius: "20px / 4px"}, |
| 67 | + {width: 500, height: 50, spread: 30, borderRadius: "15px"}, |
| 68 | + {width: 500, height: 50, spread: 30, borderRadius: "1px 1px 49px 49px"}, |
| 69 | + {width: 500, height: 50, spread: 30, borderRadius: "50%"}, |
| 70 | + {width: 500, height: 50, spread: 30, borderRadius: "50% 50% 1px 50%"}, |
| 71 | +]; |
| 72 | +function show() { |
| 73 | + output.textContent = ""; |
| 74 | + for (let testCase of testCases) { |
| 75 | + output.append(JSON.stringify(testCase)); |
| 76 | + const inner = document.createElement("div"); |
| 77 | + inner.style.width = testCase.width + "px"; |
| 78 | + inner.style.height = testCase.height + "px"; |
| 79 | + inner.style.borderRadius = testCase.borderRadius; |
| 80 | + inner.style.backgroundColor = "#fff"; |
| 81 | + const outer = document.createElement("div"); |
| 82 | + outer.appendChild(inner); |
| 83 | + if (algorithm.value === "do-not-polyfill") { |
| 84 | + inner.style.boxShadow = `0 0 0 ${testCase.spread}px #000`; |
| 85 | + outer.style.padding = testCase.spread + "px"; |
| 86 | + output.appendChild(outer); |
| 87 | + } else { |
| 88 | + outer.style.backgroundColor = "#000"; |
| 89 | + outer.style.borderStyle = "solid"; |
| 90 | + outer.style.borderWidth = testCase.spread + "px"; |
| 91 | + outer.style.width = "max-content"; |
| 92 | + output.appendChild(outer); |
| 93 | + outer.style.borderRadius = resolve(testCase, inner); |
| 94 | + } |
| 95 | + output.append(document.createElement("hr")); |
| 96 | + } |
| 97 | +} |
| 98 | +function resolve(testCase, element) { |
| 99 | + function parseLength(value, percentageBasis) { |
| 100 | + if (value.endsWith("%")) { |
| 101 | + return parseFloat(value) * percentageBasis / 100; |
| 102 | + } |
| 103 | + return parseFloat(value); |
| 104 | + } |
| 105 | + function parseCorner(value) { |
| 106 | + const raw = value.split(" "); |
| 107 | + if (raw.length === 1) { |
| 108 | + raw[1] = raw[0]; |
| 109 | + } |
| 110 | + return
AAB7
[ |
| 111 | + parseLength(raw[0], testCase.width), |
| 112 | + parseLength(raw[1], testCase.height), |
| 113 | + ]; |
| 114 | + } |
| 115 | + const cs = getComputedStyle(element); |
| 116 | + const radii = { |
| 117 | + topLeft: parseCorner(cs.borderTopLeftRadius), |
| 118 | + topRight: parseCorner(cs.borderTopRightRadius), |
| 119 | + bottomLeft: parseCorner(cs.borderBottomLeftRadius), |
| 120 | + bottomRight: parseCorner(cs.borderBottomRightRadius), |
| 121 | + }; |
| 122 | + const f = Math.min( |
| 123 | + testCase.width / (radii.topLeft[0] + radii.topRight[0]), |
| 124 | + testCase.width / (radii.bottomLeft[0] + radii.bottomRight[0]), |
| 125 | + testCase.height / (radii.topLeft[1] + radii.bottomLeft[1]), |
| 126 | + testCase.height / (radii.topRight[1] + radii.bottomRight[1]) |
| 127 | + ); |
| 128 | + if (f < 1) { |
| 129 | + for (let corner in radii) { |
| 130 | + radii[corner] = radii[corner].map(v => v * f); |
| 131 | + } |
| 132 | + } |
| 133 | + let r; |
| 134 | + switch (algorithm.value) { |
| 135 | + case "increase-by-spread": { |
| 136 | + const map = (value) => value + testCase.spread; |
| 137 | + r = { |
| 138 | + topLeft: radii.topLeft.map(map), |
| 139 | + topRight: radii.topRight.map(map), |
| 140 | + bottomLeft: radii.bottomLeft.map(map), |
| 141 | + bottomRight: radii.bottomRight.map(map), |
| 142 | + }; |
| 143 | + break; |
| 144 | + } |
| 145 | + case "current-spec": { |
| 146 | + const map = (value) => { |
| 147 | + if (value >= testCase.spread) { |
| 148 | + return value + testCase.spread; |
| 149 | + } |
| 150 | + let r = value / testCase.spread; |
| 151 | + return value + testCase.spread * (1 + (r - 1)**3); |
| 152 | + } |
| 153 | + r = { |
| 154 | + topLeft: radii.topLeft.map(map), |
| 155 | + topRight: radii.topRight.map(map), |
| 156 | + bottomLeft: radii.bottomLeft.map(map), |
| 157 | + bottomRight: radii.bottomRight.map(map), |
| 158 | + }; |
| 159 | + break; |
| 160 | + } |
| 161 | + case "percentage-min-axis": { |
| 162 | + const min = Math.min(testCase.width, testCase.height); |
| 163 | + const ratio = 1 + 2 * testCase.spread / min; |
| 164 | + const map = (value) => value * ratio; |
| 165 | + r = { |
| 166 | + topLeft: radii.topLeft.map(map), |
| 167 | + topRight: radii.topRight.map(map), |
| 168 | + bottomLeft: radii.bottomLeft.map(map), |
| 169 | + bottomRight: radii.bottomRight.map(map), |
| 170 | + }; |
| 171 | + break; |
| 172 | + } |
| 173 | + case "percentage-max-axis": { |
| 174 | + const min = Math.max(testCase.width, testCase.height); |
| 175 | + const ratio = 1 + 2 * testCase.spread / min; |
| 176 | + const map = (value) => value * ratio; |
| 177 | + r = { |
| 178 | + topLeft: radii.topLeft.map(map), |
| 179 | + topRight: radii.topRight.map(map), |
| 180 | + bottomLeft: radii.bottomLeft.map(map), |
| 181 | + bottomRight: radii.bottomRight.map(map), |
| 182 | + }; |
| 183 | + break; |
| 184 | + } |
| 185 | + case "percentage-same-axis": { |
| 186 | + debugger; |
| 187 | + const xRatio = 1 + 2 * testCase.spread / testCase.width; |
| 188 | + const yRatio = 1 + 2 * testCase.spread / testCase.height; |
| 189 | + const map = ([x, y]) => [x * xRatio, y * yRatio]; |
| 190 | + r = { |
| 191 | + topLeft: map(radii.topLeft), |
| 192 | + topRight: map(radii.topRight), |
| 193 | + bottomLeft: map(radii.bottomLeft), |
| 194 | + bottomRight: map(radii.bottomRight), |
| 195 | + }; |
| 196 | + break; |
| 197 | + } |
| 198 | + case "percentage-same-axis-ratio": { |
| 199 | + debugger; |
| 200 | + const xRatio = 1 + 2 * testCase.spread / testCase.width; |
| 201 | + const yRatio = 1 + 2 * testCase.spread / testCase.height; |
| 202 | + const map = ([x, y]) => [x * xRatio, y * yRatio]; |
| 203 | + const map2 = ([x, y]) => [x * yRatio, y * xRatio]; |
| 204 | + r = { |
| 205 | + topLeft: map(radii.topLeft), |
| 206 | + topRight: map(radii.topRight), |
| 207 | + bottomLeft: map(radii.bottomLeft), |
| 208 | + bottomRight: map(radii.bottomRight), |
| 209 | + }; |
| 210 | + const other = { |
| 211 | + topLeft: map2(radii.topLeft), |
| 212 | + topRight: map2(radii.topRight), |
| 213 | + bottomLeft: map2(radii.bottomLeft), |
| 214 | + bottomRight: map2(radii.bottomRight), |
| 215 | + }; |
| 216 | + const shadowWidth = testCase.width + 2 * testCase.spread; |
| 217 | + const shadowHeight = testCase.height + 2 * testCase.spread; |
| 218 | + const adjust = (prop1, prop2, idx) => { |
| 219 | + if (r[prop1][idx] < other[prop1][idx] || r[prop2][idx] < other[prop2][idx]) { |
| 220 | + const desiredGrowths = [ |
| 221 | + Math.max(0, other[prop1][idx] - r[prop1][idx]), |
| 222 | + Math.max(0, other[prop2][idx] - r[prop2][idx]), |
| 223 | + ]; |
| 224 | + const desiredGrowth = desiredGrowths[0] + desiredGrowths[1]; |
| 225 | + const available = (idx ? shadowHeight : shadowWidth) - r[prop1][idx] - r[prop2][idx]; |
| 226 | + const factor = Math.min(1, available / desiredGrowth); |
| 227 | + r[prop1][idx] += desiredGrowths[0] * factor; |
| 228 | + r[prop2][idx] += desiredGrowths[1] * factor; |
| 229 | + } |
| 230 | + } |
| 231 | + adjust("topLeft", "topRight", 0); |
| 232 | + adjust("bottomLeft", "bottomRight", 0); |
| 233 | + adjust("topLeft", "bottomLeft", 1); |
| 234 | + adjust("topRight", "bottomRight", 1); |
| 235 | + break; |
| 236 | + } |
| 237 | + default: { |
| 238 | + throw "Not implemented: " + algorithm.value; |
| 239 | + break; |
| 240 | + } |
| 241 | + } |
| 242 | + 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`; |
| 243 | +} |
| 244 | +show(); |
| 245 | +document.querySelector("form").addEventListener("change", show); |
| 246 | +</script> |
0 commit comments