Created
July 4, 2026 03:11
-
-
Save raphlinus/e5cc2ea3cfc2fafa4406cbd708c4fe52 to your computer and use it in GitHub Desktop.
Squircle test
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <html> | |
| <style> | |
| .controls { | |
| display: grid; | |
| grid-template-columns: 180px 200px 100px; | |
| } | |
| .controls input { | |
| width: 180px; | |
| } | |
| .label { | |
| justify-self: end; | |
| } | |
| </style> | |
| <div> | |
| <svg id="svg" width="500" height="400"> | |
| <path id="p" d="" fill-rule="evenodd"/> | |
| </svg> | |
| </div> | |
| <div class="controls"> | |
| <div class="label">Outer border radius:</div> | |
| <div><input type="range" id="ob" min="0" max="100" step="any"></div> | |
| <div id="obval"></div> | |
| <div class="label">Inner border adjustment:</div> | |
| <div><input type="range" id="ib" min="-1.5" max="1.5" value="0" step="any"></div> | |
| <div id="ibval"></div> | |
| <div class="label">Stroke width:</div> | |
| <div><input type="range" id="w" min="0" max="100" value="5" step="any"></div> | |
| <div id="wval"></div> | |
| <div class="label">Exponent:</div> | |
| <div><input type="range" id="exp" min="1" max="10" value="5" step="any"></div> | |
| <div id="expval"></div> | |
| </div> | |
| </div> | |
| <script src="squircle.js"></script> | |
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| class BezPath { | |
| constructor() { | |
| this.d = '' | |
| } | |
| moveto(x, y) { | |
| this.d += `M ${x} ${y}`; | |
| } | |
| lineto(x, y) { | |
| this.d += `L ${x} ${y}`; | |
| } | |
| curveto(x1, y1, x2, y2, x3, y3) { | |
| this.d += `C ${x1} ${y1} ${x2} ${y2} ${x3} ${y3}`; | |
| } | |
| squircle(x0, y0, x1, y1, r, k) { | |
| const {a, b} = squircleHalfCorner(k); | |
| const c = Math.pow(0.5, 1 / k); | |
| this.moveto(x0 + r, y0); | |
| this.lineto(x1 - r, y0); | |
| this.curveto(x1 - r * (1 - a), y0, x1 - r * (1 - c + b), y0 + r * (1 - c - b), x1 - r * (1 - c), y0 + r * (1 - c)); | |
| this.curveto(x1 - r * (1 - c - b), y0 + r * (1 - c + b), x1, y0 + r * (1 - a), x1, y0 + r); | |
| this.lineto(x1, y1 - r); | |
| this.curveto(x1, y1 - r * (1 - a), x1 - r * (1 - c - b), y1 - r * (1 - c + b), x1 - r * (1 - c), y1 - r * (1 - c)); | |
| this.curveto(x1 - r * (1 - c + b), y1 - r * (1 - c - b), x1 - r * (1 - a), y1, x1 - r, y1); | |
| this.lineto(x0 + r, y1); | |
| this.curveto(x0 + r * (1 - a), y1, x0 + r * (1 - c + b), y1 - r * (1 - c - b), x0 + r * (1 - c), y1 - r * (1 - c)); | |
| this.curveto(x0 + r * (1 - c - b), y1 - r * (1 - c + b), x0, y1 - r * (1 - a), x0, y1 - r); | |
| this.lineto(x0, y0 + r); | |
| this.curveto(x0, y0 + r * (1 - a), x0 + r * (1 - c - b), y0 + r * (1 - c + b), x0 + r * (1 - c), y0 + r * (1 - c)); | |
| this.curveto(x0 + r * (1 - c + b), y0 + r * (1 - c - b), x0 + r * (1 - a), y0, x0 + r, y0); | |
| } | |
| } | |
| // Adapted closely from https://developer.chrome.com/blog/implementing-corner-shape | |
| function squircleHalfCorner(k) { | |
| const p0 = 1.2430920942724248 | |
| const p1 = 2.010479023614843 | |
| const p2 = 0.32922901179443753 | |
| const p3 = 0.2823023142212073 | |
| const p4 = 1.3473704261055421 | |
| const p5 = 2.9149468637949814 | |
| const p6 = 0.9106507102917086 | |
| const s = Math.log2(k) | |
| const slope = p0 + (p6 - p0) * 0.5 * (1 + Math.tanh(p5 * (s - p1))) | |
| const base = 1 / (1 + Math.exp(slope * p1)) | |
| const logistic = 1 / (1 + Math.exp(slope * (p1 - s))) | |
| const a = (logistic - base) / (1 - base) | |
| const b = p2 * Math.exp(-p3 * Math.pow(s, p4)) | |
| return {a, b} | |
| } | |
| function render() { | |
| const iba = parseFloat(document.getElementById('ib').value); | |
| const ob = parseFloat(document.getElementById('ob').value); | |
| const w = parseFloat(document.getElementById('w').value); | |
| const k = parseFloat(document.getElementById('exp').value); | |
| document.getElementById('obval').textContent = ob.toFixed(3); | |
| document.getElementById('ibval').textContent = iba.toFixed(3); | |
| document.getElementById('wval').textContent = w.toFixed(3); | |
| document.getElementById('expval').textContent = k.toFixed(3); | |
| const path = document.getElementById('p'); | |
| const p = new BezPath(); | |
| p.squircle(10, 10, 490, 390, ob, k); | |
| const ib = Math.max(ob + w * (iba - 1), 0); | |
| p.squircle(10 + w, 10 + w, 490 - w, 390 - w, ib, k); | |
| path.setAttribute('d', p.d); | |
| } | |
| function init() { | |
| const ib = document.getElementById("ib"); | |
| ib.addEventListener('input', (e) => { render() }); | |
| const ob = document.getElementById("ob"); | |
| ob.addEventListener('input', (e) => { render() }); | |
| const w = document.getElementById("w"); | |
| w.addEventListener('input', (e) => { render() }); | |
| const exp = document.getElementById("exp"); | |
| exp.addEventListener('input', (e) => { render() }); | |
| render(); | |
| } | |
| init() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment