Pure CSS to make font-size responsive based on dynamic amount of characters

Css

Css Problem Overview


I know that this could be solved fairly easily with Javascript, but I'm only interested in a pure CSS solution.

I want a way to dynamically resize text so that it always fits into a fixed div. Here is the sample markup:

<div style="width: 200px; height: 1em; overflow: hidden;">
  <p>Some sample dynamic amount of text here</p>
</div>

I was thinking that maybe this could be possible by specifying the width of the container in ems, and getting the font-size to inherit that value?

Css Solutions


Solution 1 - Css

Note: This solution changes based on viewport size and not the amount of content

I just found out that this is possible using VW units. They're the units associated with setting the viewport width. There are some drawbacks, such as lack of legacy browser support, but this is definitely something to seriously consider using. Plus you can still provide fallbacks for older browsers like so:

p {
    font-size: 30px;
    font-size: 3.5vw;
}

http://css-tricks.com/viewport-sized-typography/ and https://medium.com/design-ux/66bddb327bb1

Solution 2 - Css

Edit: Watch out for attr() Its related to calc() in css. You may be able to achieve it in future.

Unfortunately for now there isn't a css only solution. This is what I suggest you do. To your element give a title attribute. And use text-overflow ellipsis to prevent breakage of the design and let user know more text is there.

<div style="width: 200px; height: 1em; text-overflow: ellipsis;" title="Some sample dynamic amount of text here">
 Some sample dynamic amount of text here
</div>

.

.

.

Alternatively, If you just want to reduce size based on the viewport. CSS3 supports new dimensions that are relative to view port.

body {
   font-size: 3.2vw;
}
  1. 3.2vw = 3.2% of width of viewport
  2. 3.2vh = 3.2% of height of viewport
  3. 3.2vmin = Smaller of 3.2vw or 3.2vh
  4. 3.2vmax = Bigger of 3.2vw or 3.2vh see css-tricks.com/.... and also look at caniuse.com/....

Solution 3 - Css

You might be interested in the calc approach:

font-size: calc(4vw + 4vh + 2vmin);

done. Tweak values till matches your taste.

Source: https://codepen.io/CrocoDillon/pen/fBJxu

Solution 4 - Css

The only way would probably be to set different widths for different screen sizes, but this approach is pretty inacurate and you should use a js solution.

h1 {
    font-size: 20px;
}

@media all and (max-device-width: 720px){
    h1 {
        font-size: 18px;
    }
}

@media all and (max-device-width: 640px){
    h1 {
        font-size: 16px;
    }
}

@media all and (max-device-width: 320px){
    h1 {
        font-size: 12px;
    }
}

Solution 5 - Css

For reference, a non-CSS solution:

Below is some JS that re-sizes a font depending on the text length within a container.

Codepen with slightly modified code, but same idea as below:

function scaleFontSize(element) {
    var container = document.getElementById(element);

    // Reset font-size to 100% to begin
    container.style.fontSize = "100%";

    // Check if the text is wider than its container,
    // if so then reduce font-size
    if (container.scrollWidth > container.clientWidth) {
    	container.style.fontSize = "70%";
    }
}

For me, I call this function when a user makes a selection in a drop-down, and then a div in my menu gets populated (this is where I have dynamic text occurring).

    scaleFontSize("my_container_div");

In addition, I also use CSS ellipses ("...") to truncate yet even longer text too, like so:

#my_container_div {
    width: 200px; /* width required for text-overflow to work */
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

So, ultimately:

  • Short text: e.g. "APPLES"

    Fully rendered, nice big letters.

  • Long text: e.g. "APPLES & ORANGES"

    Gets scaled down 70%, via the above JS scaling function.

  • Super long text: e.g. "APPLES & ORANGES & BANAN..."

    Gets scaled down 70% AND gets truncated with a "..." ellipses, via the above JS scaling function together with the CSS rule.

You could also explore playing with CSS letter-spacing to make text narrower while keeping the same font size.

Solution 6 - Css

calc(42px + (60 - 42) * (100vw - 768px) / (1440 - 768));

use this equation.

For anything larger or smaller than 1440 and 768, you can either give it a static value, or apply the same approach.

The drawback with vw solution is that you cannot set a scale ratio, say a 5vw at screen resolution 1440 may ended up being 60px font-size, your idea font size, but when you shrink the window width down to 768, it may ended up being 12px, not the minimal you want. With this approach, you can set your upper boundary and lower boundary, and the font will scale itself in between.

Solution 7 - Css

As many mentioned in comments to @DMTinter's post, the OP was asking about the number ("amount") of characters changing. He was also asking about CSS, but as @Alexander indicated, "it is not possible with only CSS". As far as I can tell, that seems to be true at this time, so it also seems logical that people would want to know the next best thing.

I'm not particularly proud of this, but it does work. Seems like an excessive amount of code to accomplish it. This is the core:

function fitText(el){
  var text = el.text();
  var fsize = parseInt(el.css('font-size'));
  var measured = measureText(text, fsize);

  if (measured.width > el.width()){
    console.log('reducing');
    while(true){
      fsize = parseInt(el.css('font-size'));
      var m = measureText(text, fsize );
      if(m.width > el.width()){
        el.css('font-size', --fsize + 'px');
      }
      else{
        break;
      }
    }
  }
  else if (measured.width < el.width()){
    console.log('increasing');
    while(true){
      fsize = parseInt(el.css('font-size'));
      var m = measureText(text, fsize);
      if(m.width < el.width()-4){ // not sure why -4 is needed (often)
        el.css('font-size', ++fsize + 'px');
      }
      else{
        break;
      }
    }
  }
}

Here's a JS Bin: http://jsbin.com/pidavon/edit?html,css,js,console,output
Please suggest possible improvements to it (I'm not really interested in using canvas to measure the text...seems like too much overhead(?)).

Thanks to @Pete for measureText function: https://stackoverflow.com/a/4032497/442665

Solution 8 - Css

Try RFS (for responsive font size) library by MartijnCuppens that maybe will be implemented in Bootstrap

Solution 9 - Css

This solution might also help :

$(document).ready(function () {
    $(window).resize(function() {
        if ($(window).width() < 600) {
            $('body').css('font-size', '2.8vw' );
        } else if ($(window).width() >= 600 && $(window).width() < 750) {
            $('body').css('font-size', '2.4vw');
        } 
         // and so on... (according to our needs)
        } else if ($(window).width() >= 1200) {
            $('body').css('font-size', '1.2vw');
        }
    }); 
  });

It worked for me well !

Solution 10 - Css

Why not set the class on the server side based on the number of characters?

    .NotMuchText {
        font-size: 20px;
    }

    .LotsOfText {
        font-size: 10px;
    }

I also wanted a non-javascript solution, CSS solution, and resorted to a PHP/CSS solution instead.

Solution 11 - Css

ok, your dynamic text must've come from some place. In my case this looks like:

<div class="large" :data-contentlength="Math.floor(item.name.length/7)">[[ item.name ]]</div>

and my css classes:

.large[data-contentlength="1"]{ font-size: 1.2em; }
.large[data-contentlength="2"]{ font-size: 1.1em; }
.large[data-contentlength="3"]{ font-size: 1.0em; }
.large[data-contentlength="4"]{ font-size: 0.9em; }
.large[data-contentlength="5"]{ font-size: 0.8em; }
.large[data-contentlength="6"]{ font-size: 0.7em; }
.large[data-contentlength="7"]{ font-size: 0.6em; }

I also have classes for "non-large" text:

[data-length="1"]{ font-size: 1.00em; }
...

edit: this becomes a bit easier when attr() is available in all browsers: https://developer.mozilla.org/en-US/docs/Web/CSS/attr#browser_compatibility

also, this is could be more dynamic if css could divide 2 unit-values (e.g. px and ch), for now this must be done manually.

see here:

https://jsfiddle.net/qns0pov2/3/

create a 1ch cube and see how large it is in your target unit (in the fiddle its px), calculate the amount of characters per line and use that value to get the perfect font-size for each content length.

the fiddle also shows the problems with that approach: the average character width is less than 1ch (which is based on 0) but there are characters like M which are larger (somewhere around 70%).

so if you wish to guarantee that the characters fit in the space adjust the fiddle such that: --ch-width: calc(8.109 * 1.7);

if you're more interested in the average case: --ch-width: calc(8.109 * 0.92);

Solution 12 - Css

If doing from scratch in Bootstrap 4

  1. Go to https://bootstrap.build/app
  2. Click Search Mode
  3. Search for $enable-responsive-font-sizes and turn it on.
  4. Click Export Theme to save your custom bootstrap CSS file.

Solution 13 - Css

I got this dynamic font size calc() from BootStrap somewhere and tweaked it to suit. Basing off 4pt system and rem https://www.finsweet.com/client-first/docs/sizes for a Webflow project:

html {font-size: 16px;}

@import url('https://fonts.googleapis.com/css2?family=Poppins&display=swap');
body {font-family: 'Poppins', sans-serif;}

/*---SETUP BASE SIZE ---*/
html {font-size: 16px;}

/*---LINE-HEIGHTS + MARGINS---*/
[class^="display"], h1, h2, h3, h4 {
    margin-top: 0;
    margin-bottom: 1rem;
    font-weight: 600;
}

.display-1, .display-2, .display-3, .display-4 {
    line-height: 1.2;
    
}

h1, h2, h3, h4 {
    line-height: 1.4;
}

p, ul, ol {
    margin-bottom: 0.7rem;
    line-height: 1.45;
}

.lead {
    margin-bottom: 1rem;
    line-height: 1.4;
}

/*---FONT SIZES 1279px DOWN---*/
@media (max-width: 1279px) {
    .display-1 {
        font-size: calc(1.625rem + 4.5vw);
    }

    .display-2 {
        font-size: calc(1.575rem + 3.9vw);
    }

    .display-3 {
        font-size: calc(1.525rem + 3.3vw);
    }

    .display-4 {
        font-size: calc(1.475rem + 2.7vw);
    }
  
    /*---HEADINGS---*/
    h1 {
        font-size: calc(1.375rem + 1.5vw);
    }

    h2 {
        font-size: calc(1.325rem + 0.9vw);
    }

    h3 {
        font-size: calc(1.3rem + 0.6vw);
    }

    h4 {
        font-size: calc(1.275rem + 0.3vw);
    }

    /*---PARAGRAPHS/UL/OL---*/
    p, ul, ol  {
        font-size: calc(0.823rem + 0.3vw);
    }

    .lead {
        font-size: calc(1.01rem + 0.3vw);
    }
}

/*---FONT SIZES ABOVE 1279px---*/
@media screen and (min-width: 1280px) {
    .display-1 {
        font-size: 5.22rem;
    }

    .display-2 {
        font-size: 4.7rem;
    }

    .display-3 {
        font-size: 4.16rem;
    }

    .display-4 {
        font-size: 3.63rem;
    }
    /*---HEADINGS---*/
    h1 {
        font-size: 2.58rem;
    }

    h2 {
        font-size: 2.05rem;
    }

    h3 {
        font-size: 1.78rem;
    }

    h4 {
        font-size: 1.52rem;
    }

    p, ul, ol {
        font-size: 1.0625rem;
    }

    .lead {
        font-size: 1.25rem;
    }
}

<section>
    <div class="container">
        <p style="color:#8C8C8C;"><i>Note: Resize window too see text grow/shrink in browser window <= 1279px</i></p>
        <br>
        <h1 class="display-1">Display 1</h1>
        <h1 class="display-2">Display 2</h1>
        <h1 class="display-3">Display 3</h1>
        <h1 class="display-4">Display 4</h1>
        <br>
        <br>
        <br>
        <br>
        <h1>h1. The quick brown fox jumps over the lazy dog</h1>
        <h2>h2. The quick brown fox jumps over the lazy dog</h2>
        <h3>h3. The quick brown fox jumps over the lazy dog</h3>
        <h4>h4. The quick brown fox jumps over the lazy dog</h4>
        <p>The earliest known appearance of the phrase was in The Boston Journal. In an article titled "Current Notes" in the February 9, 1885, edition, the phrase is mentioned as a good practice sentence for writing students: "A favorite copy set by writing teachers for their pupils is the following, because it contains every letter of the alphabet: 'A quick brown fox jumps over the lazy dog.'"[2] Dozens of other newspapers published the phrase over the next few months, all using the version of the sentence starting with "A" rather than "The"</p>
        <p>The earliest known use of the phrase starting with "The" is from the 1888 book Illustrative Shorthand by Linda Bronson.[4] The modern form (starting with "The") became more common even though it is slightly longer than the original (starting with "A").</p>
        <p>A 1908 edition of the Los Angeles Herald Sunday Magazine records that when the New York Herald was equipping an office with typewriters "a few years ago", staff found that the common practice sentence of "now is the time for all good men to come to the aid of the party" did not familiarize typists with the entire alphabet, and ran onto two lines in a newspaper column. They write that a staff member named Arthur F. Curtis invented the "quick brown fox" pangram to address this.</p>
        <br>
        <br>
        <br>
        <br>
        <p class="lead">Lead paragraph: As the use of typewriters grew in the late 19th century.</p>
        <p>The phrase began appearing in typing lesson books as a practice sentence. Early examples include How to Become Expert in Typewriting: A Complete Instructor Designed Especially for the Remington Typewriter (1890),[6] and Typewriting Instructor and Stenographer's Hand-book (1892). By the turn of the 20th century, the phrase had become widely known. In the January 10, 1903, issue of Pitman's Phonetic Journal, it is referred to as "the well known memorized typing line embracing all the letters of the alphabet".</p>
        <p>Robert Baden-Powell's book Scouting for Boys (1908) uses the phrase as a practice sentence for signaling.</p>
        <p>The first message sent on the Moscow–Washington hotline on August 30, 1963, was the test phrase "THE QUICK BROWN FOX JUMPED OVER THE LAZY DOG'S BACK 1234567890".</p>
        <br>
        <br>
        <br>
        <br>
        <ul class="list-unordered">
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
        </ul>
        <br>
        <br>
        <br>
        <br>
        <ol class="list-ordered">
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
            <li>During the 20th century, technicians tested typewriters and teleprinters by typing the sentence.</li>
        </ol>
        <br>
        <br>
        <br>
        <br>
    </div>
</section>

Enjoy

Solution 14 - Css

Create a lookup table that computes font-size based on the length of the string inside your <div>.

const fontSizeLookupTable = () => {
  // lookup table looks like: [ '72px', ..., '32px', ..., '16px', ..., ]
  let a = [];
  // adjust this based on how many characters you expect in your <div>
  a.length = 32;
  // adjust the following ranges empirically
  a.fill( '72px' ,     );
  a.fill( '32px' , 4 , );
  a.fill( '16px' , 8 , );
  // add more ranges as necessary
  return a;
}

const computeFontSize = stringLength => {
  const table = fontSizeLookupTable();
  return stringLength < table.length ? table[stringLength] : '16px';
}

Adjust and tune all parameters by empirical test.

Solution 15 - Css

I used smth like this:

1.style.fontSize = 15.6/(document.getElementById("2").innerHTML.length)+ 'vw'

Where: 1 - parent div Id and 2 - Id of div with my text

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDMTintnerView Question on Stackoverflow
Solution 1 - CssDMTintnerView Answer on Stackoverflow
Solution 2 - CssaWebDeveloperView Answer on Stackoverflow
Solution 3 - CssKhoPhiView Answer on Stackoverflow
Solution 4 - CssSven RojekView Answer on Stackoverflow
Solution 5 - CssMarsAndBackView Answer on Stackoverflow
Solution 6 - CssShuweiView Answer on Stackoverflow
Solution 7 - CssjbobbinsView Answer on Stackoverflow
Solution 8 - CssFred KView Answer on Stackoverflow
Solution 9 - CssGauravView Answer on Stackoverflow
Solution 10 - CssMickIsMeView Answer on Stackoverflow
Solution 11 - CssSoraphisView Answer on Stackoverflow
Solution 12 - Cssjoym8View Answer on Stackoverflow
Solution 13 - CssKerry7777View Answer on Stackoverflow
Solution 14 - CssLet Me Tink About ItView Answer on Stackoverflow
Solution 15 - CssIGreenView Answer on Stackoverflow