Linearly Scale font-size with CSS clamp() Based on the Viewport
Responsive typography has tried many methods in the past, such as media queries and CSS calc().
This article will explore a different approach, which is to scale text linearly between the minimum and maximum sizes as viewport width increases, with the goal of making its behavior more predictable at different screen sizes—all with just one line of CSS code, thanks to clamp()
.
The CSS function clamp()
is powerful. It works for all purposes, but is especially useful for typography. It works as follows: It accepts three values:
<code>clamp(minimum, preferred, maximum);</code>
The value it returns will be the preferred value until the preferred value is below the minimum value (the minimum value will be returned) or above the maximum value (the maximum value will be returned).
So, assuming you don't set a weird value and set it between the minimum and maximum values, is it always the preferred value? Well, you should use formulas with preferred values, for example:
<code>.banner { width: clamp(200px, 50% 20px, 800px); /* 是的,您可以在clamp() 中进行数学运算!*/ }</code>
Suppose you want to set the minimum font size of the element to 1 rem when the viewport width is 360px or below and the maximum font size is 3.5 rem when the viewport width is 840px or above.
in other words:
<code>1rem = 360px 及以下缩放= 361px - 839px 3.5rem = 840px 及以上</code>
Any viewport width between 361 and 839 pixels requires a font size that is linearly scaled between 1rem and 3.5rem. Using clamp()
is actually very easy ! For example, with a viewport width of 600 pixels (between 360 and 840 pixels), we will get the intermediate value between 1rem and 3.5rem, i.e. 2.25rem.
The goal we try to achieve with clamp()
is called linear interpolation : gets the intermediate information between two data points.
Here are four steps to do this:
Step 1
Select the minimum and maximum font sizes, as well as the minimum and maximum viewport widths. In our example, the font size is 1rem and 3.5rem and the width is 360px and 840px.
Step 2
Convert width to rem. Since 1rem on most browsers is 16px by default (more on that later), we will use it. Therefore, the minimum and maximum viewport widths will be 22.5 rem and 52.5 rem, respectively.
Step 3
Here we will be slightly biased towards the math aspect. When combined, the viewport width and font size form two points on the X and Y coordinate systems, and these points form a line.
We need this line—more specifically, we need its slope and its intersection with the Y axis. Here is the calculation method:
<code>slope = (maxFontSize - minFontSize) / (maxWidth - minWidth) yAxisIntersection = -minWidth * slope minFontSize</code>
This leads us to a slope value of 0.0833 and a Y-axis intersection value of -0.875.
Step 4
Now we build clamp()
function. The formula for the preferred value is:
<code>preferredValue = yAxisIntersection[rem] (slope * 100)[vw]</code>
So, the function ends up like this:
<code>.header { font-size: clamp(1rem, -0.875rem 8.333vw, 3.5rem); }</code>
You can visualize the results in the following demonstration:
Keep going and try it. As you can see, when the viewport width is 840px, the font size stops growing, and when the viewport width is 360px, the font size stops decreasing. Everything in between changes in a linear way.
What if the user changes the root font size?
You may have noticed a small flaw in this approach: it works only if the root font size is what you think (16px in the previous example) and never changes.
We convert widths 360px and 840px into rem units by dividing it by 16, because we assume that this is the root font size. If the user sets its preference to another root font size, such as 18px instead of the default 16px, the calculation will go wrong and the text won't resize as we expected.
We only have one way to use here, which is (1) do the necessary calculations in the code when the page is loading, (2) listen for changes in the root font size, and (3) recalculate everything if anything changes occur.
Here is a useful JavaScript function to perform calculations:
// Get the viewport width in pixels and the font size in rem function clampBuilder(minWidthPx, maxWidthPx, minFontSize, maxFontSize) { const root = document.querySelector("html"); const pixelsPerRem = Number(getComputedStyle(root).fontSize.slice(0, -2)); const minWidth = minWidthPx / pixelsPerRem; const maxWidth = maxWidthPx / pixelsPerRem; const slope = (maxFontSize - minFontSize) / (maxWidth - minWidth); const yAxisIntersection = -minWidth * slope minFontSize; return `clamp(${minFontSize}rem, ${yAxisIntersection}rem ${slope * 100}vw, ${maxFontSize}rem)`; } // clampBuilder(360, 840, 1, 3.5) -> "clamp(1rem, -0.875rem 8.333vw, 3.5rem)"
I deliberately omitted how to inject the returned string into CSS because there are many ways to do this depending on your needs and whether you are using native CSS, CSS-in-JS library or something else. Also, there are no native events for font size changes, so we have to check manually. We can use setInterval
to check once a second, but this may affect performance.
This is more like an extreme situation. Few people change the font size of their browsers, and even fewer people change it when visiting your website. But if you want your website to be as responsive as possible, then this is the best way to do it.
For those who don't mind extreme situations
Update: The resources shared here have stopped working since this article was first published. If you are looking for a calculator to help determine font sizes under various viewports, consider using Fluid Type Generator, which utilizes modern fluid typography.
How to avoid text readjustment
Such meticulous control over the typography size allows us to do other cool things—such as preventing text from re-adjusting at different viewport widths.
This is the normal behavior of text.
But now, with our control, we can keep the text the same number of lines, always wrapping lines at the same word, no matter what viewport width we use.
So how can we do this? First, the ratio between font size and viewport width must remain the same. In this example, we change from 1rem at 320px to 3rem at 960px.
<code>320 / 1 = 320 960 / 3 = 320</code>
If we use clampBuilder()
function written earlier, it will become:
const text = document.querySelector("p"); text.style.fontSize = clampBuilder(320, 960, 1, 3);
It maintains the same width to font ratio. The reason we do this is that we need to make sure the text has the correct size at each width so that we can maintain the same number of rows. It will still be readjusted at different widths, but this is necessary for the next work we have to do.
Now we have to get some help from the CSS character (ch) units, because having the correct font size is not enough. A ch unit is equivalent to the width of the glyph "0" in the element font. We want to make the text body as wide as the viewport, not by setting width: 100%
, but by using width: Xch
, where X is the number of ch units (or 0) required to fill the viewport horizontally.
To find X, we must divide the minimum viewport width 320px by the ch size of the element at the font size when the viewport width is 320px. In this case it is 1 rem.
Don't worry, here is a code snippet to calculate the ch size of the element:
// Returns the width of the element's "0" glyph (in pixels) at the desired font size function calculateCh(element, fontSize) { const zero = document.createElement("span"); zero.innerText = "0"; zero.style.position = "absolute"; zero.style.fontSize = fontSize; element.appendChild(zero); const chPixels = zero.getBoundingClientRect().width; element.removeChild(zero); return chPixels; }
Now we can continue to set the width of the text:
function calculateCh(element, fontSize) { ... } const text = document.querySelector("p"); text.style.fontSize = clampBuilder(320, 960, 1, 3); text.style.width = `${320 / calculateCh(text, "1rem")}ch`;
Wow, wait. Something bad happened. There is a horizontal scroll bar that messes things up!
When we talk about 320px, we are talking about the width of the viewport, including the vertical scrollbar. Therefore, the width of the text is set to the width of the visible area plus the width of the scroll bar, which makes it horizontally overflow.
So why not use a measure that does not contain the width of the vertical scroll bar? We can't, it's because of the CSS vw unit. Remember, we are using vw in clamp()
to control the font size. You will see that vw includes the width of the vertical scrollbar, which makes the font scale along with the viewport width, including the scrollbar. If we want to avoid any re-adjustment, the width must be proportional to the viewport width, including the scroll bar.
So what should we do? When we do:
text.style.width = `${320 / calculateCh(text, "1rem")}ch`;
…We can narrow down the result by multiplying it by a number less than 1. 0.9 can solve the problem. This means that the width of the text will be 90% of the viewport width, which will be enough to make up for the small space the scrollbar takes up. We can make it narrower by using smaller numbers (e.g. 0.6).
function calculateCh(element, fontSize) { ... } const text = document.querySelector("p"); text.style.fontSize = clampBuilder(20, 960, 1, 3); text.style.width = `${320 / calculateCh(text, "1rem") * 0.9}ch`;
You might tend to simply subtract a few pixels from 320 to ignore the scrollbar, like so:
text.style.width = `${(320 - 30) / calculateCh(text, "1rem")}ch`;
The problem with doing this is that it will restore the retuning problem! This is because subtracting from 320 will destroy the viewport to font ratio.
The width of the text must always be a percentage of the viewport width. Another thing to note is that we need to make sure we load the same font on every device that uses the site. This sounds obvious, doesn't it? Well, here is a small detail that might deviate your text. Doing things like font-family: sans-serif
does not guarantee the same fonts are used in every browser. sans-serif
will set Arial on Chrome for Windows, but Roboto on Chrome for Android. Additionally, the geometry of some fonts can cause readjustment, even if you do everything right. Monowidth fonts tend to produce the best results. Therefore, be sure to make sure your fonts are accurate.
See this non-retuning example in the following demonstration:
Non-realization text in container
What we have to do now is apply the font size and width to the container, not directly to the text element. The text in it only needs to be set to width: 100%
. For paragraphs and titles, this is not necessary because they are block-level elements themselves and will automatically fill the width of the container.
One advantage of applying this method in a parent container is that its children will automatically respond and resize without setting their font size and width one by one. Also, if we need to change the font size of a single element without affecting the others, we simply change its font size to any em value and it will naturally be relative to the font size of the container.
Non-retuning text is picky, but it is a subtle effect that can bring good results to the design!
Summarize
To summarize, I put together a small demonstration of how all this looks in a real-life scenario.
In this final example, you can also change the root font size and clamp()
function will automatically recalculate so that the text will have the correct size in any case.
Although the goal of this article is to use clamp()
with font size, this same technique can be used to receive any CSS attributes of length units. Now, I'm not saying you should use it anywhere. Many times, a good font-size: 1rem
is enough. I just want to show you how much control you can have when you need it .
Personally, I believe clamp()
is one of the best things in CSS and I can’t wait to see what other uses people will come up with as it becomes more and more widely used!
The above is the detailed content of Linearly Scale font-size with CSS clamp() Based on the Viewport. For more information, please follow other related articles on the PHP Chinese website!

Hot AI Tools

Undresser.AI Undress
AI-powered app for creating realistic nude photos

AI Clothes Remover
Online AI tool for removing clothes from photos.

Undress AI Tool
Undress images for free

Clothoff.io
AI clothes remover

AI Hentai Generator
Generate AI Hentai for free.

Hot Article

Hot Tools

Notepad++7.3.1
Easy-to-use and free code editor

SublimeText3 Chinese version
Chinese version, very easy to use

Zend Studio 13.0.1
Powerful PHP integrated development environment

Dreamweaver CS6
Visual web development tools

SublimeText3 Mac version
God-level code editing software (SublimeText3)

Hot Topics



If you’ve recently started working with GraphQL, or reviewed its pros and cons, you’ve no doubt heard things like “GraphQL doesn’t support caching” or

With the recent climb of Bitcoin’s price over 20k $USD, and to it recently breaking 30k, I thought it’s worth taking a deep dive back into creating Ethereum

No matter what stage you’re at as a developer, the tasks we complete—whether big or small—make a huge impact in our personal and professional growth.

It's out! Congrats to the Vue team for getting it done, I know it was a massive effort and a long time coming. All new docs, as well.

I'd say "website" fits better than "mobile app" but I like this framing from Max Lynch:

I had someone write in with this very legit question. Lea just blogged about how you can get valid CSS properties themselves from the browser. That's like this.

The other day, I spotted this particularly lovely bit from Corey Ginnivan’s website where a collection of cards stack on top of one another as you scroll.

I was just chatting with Eric Meyer the other day and I remembered an Eric Meyer story from my formative years. I wrote a blog post about CSS specificity, and
