length - resize text to fit div css




Font scaling based on width of container (20)

I'm having a hard time getting my head around font scaling.

I currently have this site with a body font-size of 100%. 100% of what though? This seems to compute out at 16px.

I was under the impression that 100% would somehow refer to the size of the browser window, but apparently not because it's always 16px whether the window is resized down to a mobile width or full blown widescreen desktop.

How can I make the text on my site scale in relation to its container? I tried using em but this doesn't scale either.

My reasoning is that things like my menu become squished when you resize, so I need to reduce the px font-size of .menuItem among other elements in relation to the width of the container. (E.g in the menu on a large desktop, 22px works perfectly. Move down to tablet width and 16px is more appropriate.)

I'm aware I can add breakpoints, but I really want the text to scale as WELL as having extra breakpoints, otherwise I'll end up with hundreds of breakpoints for every 100px decrease in width to control the text.


Pure-CSS solution with calc(), CSS units and math

This is precisely not what OP asks, but may make someone's day. This answer is not spoon-feedingly easy and needs some researching in developer end.

I came finally to get a pure-CSS solution for this using calc() with different units. You will need some basic mathematical understanding of formulas to work out your expression for calc().

When I worked this out, I had to get a full-page-width responsive header with some padding few parents up in DOM. I'll use my values here, replace them with your own.

To mathematics

You will need:

  • nicely adjusted ratio in some viewport, I used 320px, thus I got 24px high and 224px wide so ratio is 9.333... or 28 / 3
  • the container width, I had padding: 3em and full width so this got to 100wv - 2 * 3em

X is the width of container so replace it with your own expression or adjust the value to get full-page text. R is the ratio you will have. You can get it by adjusting the values in some viewport, inspecting element width and height and replacing them with your own values. Also, it is width / heigth ;)

x = 100vw - 2 * 3em = 100vw - 6em
r = 224px/24px = 9.333... = 28 / 3

y = x / r
  = (100vw - 6em) / (28 / 3)
  = (100vw - 6em) * 3 / 28
  = (300vw - 18em) / 28
  = (75vw - 4.5rem) / 7

And bang! It worked! I wrote

font-size: calc((75vw - 4.5rem) / 7)

to my header and it adjusted nicely in every viewport.

But how does it work?

We need some constants up here. 100vw means the full width of viewport, and my goal was to establish full-width header with some padding.

The ratio. Getting a width and height in one viewport got me a ratio to play with, and with ratio I know what the height should be in other viewport width. Calculating them with hand would take plenty of time and at least take lots of bandwith so it's not a good answer.

Conclusion

I wonder why no-one has figured this out and some people are even telling that this would be impossible to tinker with CSS. I don't like to use Javascript in adjusting elements, so I don't accept JS or even jQuery answers without digging more. All in all, it's good that this got figured out and this is one step to pure-CSS implementations in website design.

I apologize of any unusual convention in my text, I'm not native speaker in english and also quite new to writing SO answers.

Edit: it should also be noted that we have evil scrollbars in some browsers. For example, when using Firefox I noticed that 100vw means the full width of viewport, extending under scrollbar (where content cannot expand!), so the fullwidth text has to be margined carefully and preferably get tested with many browsers and devices.


Use CSS Variables

No one has mentioned CSS variables yet, and this approach worked best for me, so:

Let's say you've got a column on your page that is 100% of the width of a mobile user's screen, but has a max-width of 800px, so on desktop there's some space on either side of the column. Put this at the top of your page:

<script> document.documentElement.style.setProperty('--column-width', Math.min(window.innerWidth, 800)+'px'); </script>

And now you can use that variable (instead of the built-in vw unit) to set the size of your font. E.g.

p {
  font-size: calc( var(--column-width) / 100 );
}

It's not a pure CSS approach, but it's pretty close.


100% is relative to the base font size, which if you haven't set it would be the browser's user-agent default.

To get the effect you're after I would use a piece of javascript to adjust the base font size relative to the window dimensions.


This web component changes the font size so the inner text width matches the container width. Check the demo.

You can use it like this:

<full-width-text>Lorem Ipsum</full-width-text>

EDIT: If the container is not the body CSS Tricks covers all of your options here: https://css-tricks.com/fitting-text-to-a-container/

If the container is the body, what you are looking for is Viewport-percentage lengths:

The viewport-percentage lengths are relative to the size of the initial containing block. When the height or width of the initial containing block is changed, they are scaled accordingly. However, when the value of overflow on the root element is auto, any scroll bars are assumed not to exist.

The values are:

  • vw (% of the viewport width)
  • vh (% of the viewport height)
  • vi (1% of the viewport size in the direction of the root element's inline axis)
  • vb (1% of the viewport size in the direction of the root element's block axis)
  • vmin (the smaller of vw or vh)
  • vmax (the larger or vw or vh)

1 v* is equal to 1% of the initial containing block.

using it looks like this:

p {
    font-size: 4vw;
}

As you can see, when the viewport width increases, so does the font-size, without needing to use media queries.

These values are a sizing unit, just like px or em, so they can be used to size other elements as well, such was width, margin, or padding.

Browser support is pretty good, but you'll likely need a fallback, such as:

p {
    font-size: 16px; 
    font-size: 4vw;
}

Check out the support statistics: http://caniuse.com/#feat=viewport-units.

Also, check out CSS-Tricks for a broader look: http://css-tricks.com/viewport-sized-typography/

Here's a nice article about setting min/max sizes and exercising a bit more control over the sizes: http://madebymike.com.au/writing/precise-control-responsive-typography/

And here's an article about setting your size using calc() so that the text fills the viewport: http://codepen.io/CrocoDillon/pen/fBJxu

Also, please view this article, which uses a technique dubbed 'molten leading' to adjust the line-height as well. https://css-tricks.com/molten-leading-css/


There is a way to do this WITHOUT javascript!

You can use an inline svg. You can use css on an svg if it is inline. You have to remember that using this method means your svg will respond to its container size.

Try using the following solution...

HTML

<div>
  <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 360.96 358.98" >
      <text>SAVE $500</text>

  </svg>

</div>

CSS

div {
  width: 50%; /* set your container width */
  height: 50%; /* Set your container height */

}

svg {
  width: 100%;
  height: auto;

}

text {
  transform: translate(40px, 202px);
  font-size: 62px;
  fill: #000;

}

example: https://jsfiddle.net/k8L4xLLa/32/

Want something more flashy?

SVGs also allow you to do cool stuff with shapes and junk. Check out this great use case for scalable text...

https://jsfiddle.net/k8L4xLLa/14/


As a JavaScript fallback (or your sole solution), you can use my jQuery Scalem plugin, which lets you scale relative to the parent element (container) by passing the reference option.


For dynamic text , this plugin is quite useful http://freqdec.github.io/slabText/. Simply add css

.slabtexted .slabtext
    {
    display: -moz-inline-box;
    display: inline-block;
    white-space: nowrap;
    }
.slabtextinactive .slabtext
    {
    display: inline;
    white-space: normal;
    font-size: 1em !important;
    letter-spacing: inherit !important;
    word-spacing: inherit !important;
    *letter-spacing: normal !important;
    *word-spacing: normal !important;
    }
.slabtextdone .slabtext
    {
    display: block;
    }

and script

$('#mydiv').slabText();

I don't see any answer with reference to CSS flex property, but it can be very useful too.


I've prepared simple scale function using css transform instead of font-size. You can use it inside of any container, you don't have to set media queries etc :)

Blog post: https://blog.polarbits.co/2017/03/07/full-width-css-js-scalable-header/

The code:

function scaleHeader() {
  var scalable = document.querySelectorAll('.scale--js');
  var margin = 10;
  for (var i = 0; i < scalable.length; i++) {
    var scalableContainer = scalable[i].parentNode;
    scalable[i].style.transform = 'scale(1)';
    var scalableContainerWidth = scalableContainer.offsetWidth - margin;
    var scalableWidth = scalable[i].offsetWidth;
    scalable[i].style.transform = 'scale(' + scalableContainerWidth / scalableWidth + ')';
    scalableContainer.style.height = scalable[i].getBoundingClientRect().height + 'px';
  }
}

Working demo: https://codepen.io/maciejkorsan/pen/BWLryj


In order to make font-size fit its container, rather than the window see the resizeFont() function I have shared in this question (a combination of other answers, most of which are already linked here). It is triggered using window.addEventListener('resize', resizeFont);.

Vanilla JS: Resize font-awesome to fit container

JS:

function resizeFont() {
  var elements  = document.getElementsByClassName('resize');
  console.log(elements);
  if (elements.length < 0) {
    return;
  }
  _len = elements.length;
  for (_i = 0; _i < _len; _i++) {
    var el = elements[_i];
    el.style.fontSize = "100%";
    for (var size = 100; el.scrollHeight > el.clientHeight; size -= 10) {
      el.style.fontSize = size + '%';
    }
  }
}

You could perhaps use vw/vh as a fallback, so you dynamically assign em or rem units using javascript, ensuring that the fonts do scale to the window if javascript is disabled.

Apply the .resize class to all elements containing text you wish to be scaled.

Trigger the function prior to adding the window resize event listener. Then, any text which doesn't fit its container will be scaled down when the page loads, as well as when it is resized.

NOTE: The default font-size must be set to either em,rem or % to achieve proper results... and if the styling of your elements is too complex then the browser might crash if you start frantically resizing the window.


Inside your CSS try adding this at the bottom changing the 320px width for wherever your design starts breaking:

    @media only screen and (max-width: 320px) {

       body { font-size: 1em; }

    }

Then give the font-size in "px" or "em" as you wish.


My problem was similar but related to scaling text within a heading. I tried Fit Font but I needed to toggle the compressor to get any results, since it was solving a slightly different problem, as was Text Flow. So I wrote my own little plugin that reduces the font size to fit the container, assuming you have overflow: hidden and white-space: nowrap so that even if reducing the font to the minimum doesn't allow showing the full heading, it just cuts off what it can show.

(function($) {

  // Reduces the size of text in the element to fit the parent.
  $.fn.reduceTextSize = function(options) {
    options = $.extend({
      minFontSize: 10
    }, options);

    function checkWidth(em) {
      var $em = $(em);
      var oldPosition = $em.css('position');
      $em.css('position', 'absolute');
      var width = $em.width();
      $em.css('position', oldPosition);
      return width;
    }

    return this.each(function(){
      var $this = $(this);
      var $parent = $this.parent();
      var prevFontSize;
      while (checkWidth($this) > $parent.width()) {
        var currentFontSize = parseInt($this.css('font-size').replace('px', ''));
        // Stop looping if min font size reached, or font size did not change last iteration.
        if (isNaN(currentFontSize) || currentFontSize <= options.minFontSize ||
            prevFontSize && prevFontSize == currentFontSize) {
          break;
        }
        prevFontSize = currentFontSize;
        $this.css('font-size', (currentFontSize - 1) + 'px');
      }
    });

  };

})(jQuery);

Solution with SVG:

<div style="width: 60px;">  
  <svg width="100%" height="100%" viewBox="0 -200 1000 300"
     xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
    <text font-size="300" fill="black">Text</text>
  </svg>
</div>

https://jsfiddle.net/qc8ht5eb/


There is a big philosophy for this issue. The easiest thing to do would be to give a certain font-size to body (i recommand 10), and then all the other element would have their font in em or rem. I'll give you and example to understand those units. Em is always relative to his parent

body{font-size:10px;}
.menu{font-size:2em;} /* that means 2*10px = 20px */
.menu li{font-size:1.5em;} /* that means 1.5*20px = 30px */

Rem is always relative to body

body{font-size:10px;}
.menu{font-size:2rem;} /* that means 2*10px = 20px */
.menu li{font-size:1.5rem;} /* that means 1.5*10px = 15px */

And than you could create a script that would modify font-size relative to your container width. But this isn't what I would recomand. Because in a 900px width container for example you would have a p element with 12px font-size let's say. And on your ideea that would become on 300px width container at 4px font-size. There has to be a lower limit. Another solutions would be with media queries, so that you could set font for different widths.

But the solutions that I would recommand is to use a javascript library that helps you with that. And fittext.js that I found so far.


This may not be super practical, but if you want font to be a direct function of the parent, without having any JS that listens/loops(interval) to read the size of the div/page, there is a way to do it. Iframes. Anything within the iframe will consider the size of the iframe as the size of the viewport. So the trick is to just make an iframe whose width is the maximum width you want your text to be, and whose height is equal to the maximum height * the particular text's aspect ratio.

Setting aside the limitation that viewport units can't also come along side parent units for text (as in, having the % size behave like everyone else), viewport units do provide a very powerful tool:being able to get the min/max dimension. You can't do that anywhere else - you can't say..make the height of this div be the width of the parent * something.

That being said, the trick is to use vmin, and to set the iframe size so that [fraction] * total height is a good font size when the height is the limiting dimension, and [fraction] * total width when the width is the limiting dimension. This is why the heigh has to be a product of the width and the aspect ratio.

for my particular example, you have

.main iframe{
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100%;
  height: calc(3.5 * 100%);
  background: rgba(0,0,0,0);
  border-style: none;
  transform: translate3d(-50%,-50%,0);
}

The small annoyance with this method is that you have to manually set the CSS of the iframe. If you attach the whole CSS file, that would take up a lot of bandwidth for many text areas. So, what I do is attach the rule that I want directly from my CSS.

var rule = document.styleSheets[1].rules[4];
var iDoc = document.querySelector('iframe').contentDocument;
iDoc.styleSheets[0].insertRule(rule.cssText); 

You can write small function that gets the CSS rule / all CSS rules that would affect the text area.

I cannot think of another way to do it without having some cycling/listening JS. The real solution would be for browsers to provide a way to scale text as a function of the parent container AND to also provide the same vmin/vmax type functionality.

JS fiddle: https://jsfiddle.net/0jr7rrgm/3/ (click once to lock the red square to the mouse, click again to release)

Most of the JS in the fiddle is just my custom click-drag function


Try to use fitText plugin, cause Viewport sizes isn't the solution of this problem. Just add library

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script> <script src="jquery.fittext.js"></script>

and change font-size for correct by settings the coefficient of text:

$("#text_div").fitText(0.8);

you can set max and min values of text:

$("#text_div").fitText(0.8, { minFontSize: '12px', maxFontSize: '36px' });

Updated because I got a down vote.

Here is the function:

document.body.setScaledFont = function(f) {
  var s = this.offsetWidth, fs = s * f;
  this.style.fontSize = fs + '%';
  return this
};

Then convert all your documents child element font sizes to em's or %.

Then add something like this to your code to set the base font size.

document.body.setScaledFont(0.35);
window.onresize = function() {
    document.body.setScaledFont(0.35);
}

http://jsfiddle.net/0tpvccjt/


What I do in one of my projects is a "mixture" between vw and vh to adjust the font size to my needs, eg.:

font-size: calc(3vw + 3vh);

I know this doesn't answer the op's question, but maybe it can be a solution to anyone else.






font-size