A Pen by Matt Daniel Brown on CodePen.
Created
June 3, 2025 22:10
-
-
Save mattdanielbrown/aee154e29001a714d8d9c4c0bd876bfd to your computer and use it in GitHub Desktop.
Typescale Calculator with Clamp
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
| <div class="wrap"> | |
| <aside> | |
| <h1>Typescale Calculator <br> with Clamp</h1> | |
| <div class="controls"> | |
| <div class="control-group"> | |
| <label for="baseSize">Base Font Size (rem):</label> | |
| <input type="number" id="baseSize" value="1" step="0.01" min="0.5" max="2"> | |
| </div> | |
| <div class="control-group"> | |
| <label for="ratio">Type Scale Ratio:</label> | |
| <select id="ratio"> | |
| <option value="1.067">1.067 – Minor Second</option> | |
| <option value="1.125" selected>1.125 – Major Second</option> | |
| <option value="1.200">1.200 – Minor Third</option> | |
| <option value="1.250">1.250 – Major Third</option> | |
| <option value="1.333">1.333 – Perfect Fourth</option> | |
| <option value="1.414">1.414 – Augmented Fourth</option> | |
| <option value="1.500">1.500 – Perfect Fifth</option> | |
| <option value="1.618">1.618 – Golden Ratio</option> | |
| </select> | |
| </div> | |
| <div class="control-group"> | |
| <label for="mobileScale">Mobile Scale Factor:</label> | |
| <input type="number" id="mobileScale" value="0.8" step="0.05" min="0.5" max="1"> | |
| <small>Reduces all sizes on mobile (0.8 = 80% of desktop size)</small> | |
| </div> | |
| <div class="control-group"> | |
| <label for="vwFactor">Viewport Width Scaling:</label> | |
| <input type="number" id="vwFactor" value="0.5" step="0.1" min="0" max="2"> | |
| <small>Controls how much fonts scale with viewport (higher = more scaling)</small> | |
| </div> | |
| </div> | |
| <div class="output" id="output"></div> | |
| </aside> | |
| <div class="preview" id="preview"></div> | |
| </div> | |
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
| function calculateTypescale() { | |
| const baseSize = parseFloat(document.getElementById("baseSize").value); | |
| const ratio = parseFloat(document.getElementById("ratio").value); | |
| const mobileScale = parseFloat(document.getElementById("mobileScale").value); | |
| const vwFactor = parseFloat(document.getElementById("vwFactor").value); | |
| // Calculate sizes for each level | |
| const sizes = []; | |
| // Small sizes (negative steps) | |
| sizes.push({ | |
| name: "--font-size-1", | |
| level: -2, | |
| size: baseSize / Math.pow(ratio, 2) | |
| }); | |
| sizes.push({ | |
| name: "--font-size-2", | |
| level: -1, | |
| size: baseSize / ratio | |
| }); | |
| // Base and positive steps | |
| for (let i = 0; i <= 8; i++) { | |
| sizes.push({ | |
| name: `--font-size-${i + 3}`, | |
| level: i, | |
| size: baseSize * Math.pow(ratio, i) | |
| }); | |
| } | |
| // Display sizes (extra large) | |
| sizes.push({ | |
| name: "--font-size-display-1", | |
| level: 9, | |
| size: baseSize * Math.pow(ratio, 9) | |
| }); | |
| sizes.push({ | |
| name: "--font-size-display-2", | |
| level: 10, | |
| size: baseSize * Math.pow(ratio, 10) | |
| }); | |
| sizes.push({ | |
| name: "--font-size-display-3", | |
| level: 11, | |
| size: baseSize * Math.pow(ratio, 11) | |
| }); | |
| // Generate CSS | |
| let css = "/* Typescale: " + ratio + " */\n"; | |
| let previewHTML = ""; | |
| sizes.forEach((item) => { | |
| const desktopSize = item.size; | |
| const mobileSize = item.size * mobileScale; | |
| const vwScaling = (desktopSize - mobileSize) * vwFactor; | |
| css += `${item.name}: clamp(${mobileSize.toFixed( | |
| 3 | |
| )}rem, ${mobileSize.toFixed(3)}rem + ${vwScaling.toFixed( | |
| 2 | |
| )}vw, ${desktopSize.toFixed(3)}rem);\n`; | |
| // Add to preview | |
| previewHTML += `<div class="font-sample" contenteditable style="font-size: var(${ | |
| item.name | |
| })"> | |
| Sample Text <strong>Bold</strong> (${item.name}) | |
| <span class="font-info">Mobile: ${mobileSize.toFixed( | |
| 2 | |
| )}rem, Desktop: ${desktopSize.toFixed(2)}rem</span> | |
| </div>`; | |
| }); | |
| document.getElementById("output").textContent = css; | |
| // Update CSS custom properties for preview | |
| const root = document.documentElement; | |
| sizes.forEach((item) => { | |
| const desktopSize = item.size; | |
| const mobileSize = item.size * mobileScale; | |
| const vwScaling = (desktopSize - mobileSize) * vwFactor; | |
| root.style.setProperty( | |
| item.name, | |
| `clamp(${mobileSize}rem, ${mobileSize}rem + ${vwScaling}vw, ${desktopSize}rem)` | |
| ); | |
| }); | |
| document.getElementById("preview").innerHTML = | |
| "<h3>Live Preview:</h3>" + previewHTML; | |
| } | |
| // Initial calculation | |
| calculateTypescale(); | |
| // Update on input change | |
| document.querySelectorAll("input, select").forEach((element) => { | |
| element.addEventListener("input", calculateTypescale); | |
| }); |
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
| body { | |
| font-family: system-ui, -apple-system, sans-serif; | |
| // max-width: 800px; | |
| margin: 0 auto; | |
| padding: 2rem; | |
| line-height: 1.6; | |
| } | |
| .controls { | |
| background: #f5f5f5; | |
| padding: 1.5rem; | |
| border-radius: 8px; | |
| margin-bottom: 2rem; | |
| } | |
| .control-group { | |
| margin-bottom: 1rem; | |
| } | |
| label { | |
| display: block; | |
| margin-bottom: 0.5rem; | |
| font-weight: 600; | |
| } | |
| input, | |
| select { | |
| padding: 0.5rem; | |
| border: 1px solid #ddd; | |
| border-radius: 4px; | |
| font-size: 1rem; | |
| } | |
| .output { | |
| background: #f9f9f9; | |
| padding: 1.5rem; | |
| border-radius: 8px; | |
| font-family: "Courier New", monospace; | |
| white-space: pre-line; | |
| margin-bottom: 2rem; | |
| } | |
| .preview { | |
| border: 1px solid #ddd; | |
| border-radius: 8px; | |
| padding: 1.5rem; | |
| width: 80vw; | |
| } | |
| .font-sample { | |
| margin-bottom: 0.5rem; | |
| border-bottom: 1px solid #eee; | |
| padding-bottom: 0.5rem; | |
| } | |
| .font-info { | |
| font-size: 0.8rem; | |
| color: #666; | |
| margin-left: 1rem; | |
| } | |
| .wrap { | |
| display: flex; | |
| > * { | |
| &:first-child { | |
| width: 32vw; | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment