331

I need to display user entered text into a fixed size div. What i want is for the font size to be automatically adjusted so that the text fills the box as much as possible.

So - If the div is 400px x 300px. If someone enters ABC then it's really big font. If they enter a paragraph, then it would be a tiny font.

I'd probably want to start with a maximum font size - maybe 32px, and while the text is too big to fit the container, shrink the font size until it fits.

GeekyMonkey
  • 12,478
  • 6
  • 33
  • 39
  • I did some measurements where I changed the length of a dynamic text, and the size of the container to figure out which font size will make the text fit perfectly. And after doing some regression analysis, I've come up with a simple mathematical function that will automatically generate the best font size. – Friend of Kim Jun 01 '13 at 22:42
  • 2
    It actually turns out that the graph that gives you the best font size is given by f(x) = g(letters) * (x / 1000)^n, where g(x) is a simple function, n is varying depending on the font you are using. (Although it can have a standard value for all fonts, if you don't want to tweak it to get it absolutely perfect...). x is the size in square pixels of the container. – Friend of Kim Jun 01 '13 at 22:45
  • 1
    If you are still interested, I can add an answer. Personally I think it's a much better method to generate the correct font size in the first place, instead of trying and failing until the script "gets it right". – Friend of Kim Jun 01 '13 at 22:46
  • Of course, the trick was to get it working with two variables, both the size and the amount of text. In your case you only have the amount of text, and thus you don't even need the g(x). – Friend of Kim Jun 01 '13 at 22:47
  • @FriendofKim do you still have the specifics for your formula? What is g() and what is n actually? – EasilyBaffled Jan 20 '14 at 22:50
  • @EasilyBaffled Ultimately I used this formula in a little different manner than what this question needed. But I'll dig up the old code and throw it in here. I believe the numbers in the formula has to be tweaked for each font. – Friend of Kim Jan 21 '14 at 18:50
  • @EasilyBaffled There you go! I hope it works out for you! Happy programming :) – Friend of Kim Jan 21 '14 at 19:09
  • https://github.com/rjz/inflateText.js allows for sizing of dynamic text. It's an extension of FitText.js – dangel Jun 10 '15 at 03:24
  • You can use css3 relative units for this as long as your layout is relative to the browser’s width or height. For example, if you can make the element take up the whole width of the browser by setting font-size: 100vw. See http://www.sitepoint.com/new-css3-relative-font-size/ and https://css-tricks.com/viewport-sized-typography/ – OxC0FFEE Aug 06 '15 at 17:50
  • We were discussing this yesterday https://stackoverflow.com/questions/45655078/how-to-adjust-fontsize-to-fit-textarea-width-and-height-as-much-as-possible/45656734?noredirect=1#comment78282714_45656734 – TMOTTM Aug 13 '17 at 17:21
  • A modern Web Component ```` can be used: https://fit-text.github.io – Danny '365CSI' Engelman Jul 13 '23 at 20:15

21 Answers21

173

This is what I ended up with:

Here is a link to the plugin: https://plugins.jquery.com/textfill/
And a link to the source: http://jquery-textfill.github.io/

;(function($) {
    $.fn.textfill = function(options) {
        var fontSize = options.maxFontPixels;
        var ourText = $('span:visible:first', this);
        var maxHeight = $(this).height();
        var maxWidth = $(this).width();
        var textHeight;
        var textWidth;
        do {
            ourText.css('font-size', fontSize);
            textHeight = ourText.height();
            textWidth = ourText.width();
            fontSize = fontSize - 1;
        } while ((textHeight > maxHeight || textWidth > maxWidth) && fontSize > 3);
        return this;
    }
})(jQuery);

$(document).ready(function() {
    $('.jtextfill').textfill({ maxFontPixels: 36 });
});

and my HTML is like this

<div class='jtextfill' style='width:100px;height:50px;'>
    <span>My Text Here</span>
</div>
TylerH
  • 20,799
  • 66
  • 75
  • 101
GeekyMonkey
  • 12,478
  • 6
  • 33
  • 39
  • Note: I found that for some reason this plugin works only when the div ($('.jtextfill') in above example) is part of root document. It looks like .width() returns zero when the div is embedded inside other divs. – Jayesh Feb 11 '11 at 04:31
  • It shouldn't return zero for width. Is the element hidden at that point? Maybe you're calling it too early. – GeekyMonkey Feb 17 '11 at 20:58
  • 1
    The "while" line on that loop looks wrong to me -- there should be parentheses around the "||" subexpression. The way it's written now, the font size minimum is only checked when the width is too large, not the height. – Pointy Mar 07 '11 at 16:06
  • I like this 'cause it intrinsically supports text that's been altered via use of CSS `letter-spacing`, obliques, and so on. Bravo. – Winfield Trail Sep 01 '12 at 18:49
  • What if text within `div` is going to change dynamically - will it be recalculated automatically? If no, what is needed to make it work? – Ωmega Oct 08 '12 at 15:35
  • 4
    This approach is VERY slow, each time the font changes sizes it requires the element to be re-rendered. Check my answer for a better way to do this. – Hoffmann Jul 02 '13 at 18:50
  • Is just me or that `;` is wrong? or is it just part of the code o.O – Michel Ayres Mar 24 '14 at 17:55
  • It may be slower than some of the other solutions here, but it wraps text well, which I needed in my case. I also had to use css to set the initial font size to 3px, and then call textfill with a larger fontsize in order to prevent enormous line spacing gaps when text was shrunk. – Chris Jul 19 '14 at 13:37
  • I tried this one, and it didnt work quite well with class selector, and working with multiple classes at once. Hoffmanns answer about jquery-bigtext was a lot of help. – Sinisha Mihajlovski Nov 21 '14 at 10:52
  • @GeekyMonkey --> Do we have to put an inner span tag in a div? What about if there's no span tag? how will you deal on it? – alyssaeliyah Aug 06 '15 at 08:24
  • I changed this plugin to support fitting a multi line text in a fixed size div. I changed the height calculation in the loop from `textHeight = ourText.height();` to `textHeight = ourText[0].scrollHeight;` – NuclearCookie Jan 27 '16 at 10:08
53

I didn't find any of the previous solutions to be adequate enough due to bad performance, so I made my own that uses simple math instead of looping. Should work fine in all browsers as well.

According to this performance test case it is much faster then the other solutions found here.

(function($) {
    $.fn.textfill = function(maxFontSize) {
        maxFontSize = parseInt(maxFontSize, 10);
        return this.each(function(){
            var ourText = $("span", this),
                parent = ourText.parent(),
                maxHeight = parent.height(),
                maxWidth = parent.width(),
                fontSize = parseInt(ourText.css("fontSize"), 10),
                multiplier = maxWidth/ourText.width(),
                newSize = (fontSize*(multiplier-0.1));
            ourText.css(
                "fontSize", 
                (maxFontSize > 0 && newSize > maxFontSize) ? 
                    maxFontSize : 
                    newSize
            );
        });
    };
})(jQuery);

If you want to contribute I've added this to Gist.

mekwall
  • 28,614
  • 6
  • 75
  • 77
  • Nice script, but I think the OP is looking to fill the entirety of the text box, rather than just fitting a single line of text horizontally. Seems like that's what the Fiddle in your Gist link does. – Jon Nov 02 '11 at 20:31
  • 1
    @Jon, thanks! You are right that my script doesn't do multiple lines, but then again the OP didn't specifically ask for that so your assumption might be wrong. Also, that kind of behavior doesn't make much sense imo. I guess the best way to add multi-line support would be to split the string based on the amount of words and then calculate each part with the above script and it would most likely be faster anyway. – mekwall Nov 07 '11 at 08:05
  • Yeah, you're totally right — the OP's question is a little ambiguous. Didn't mean to jump to a conclusion so fast. For anyone looking for multiline support, I had success with sandstrom's code above. Otherwise, for single-line text, this is a great solution :) – Jon Nov 07 '11 at 20:22
  • 4
    @Jon, I played around a little with multiline textfill and ended up with [this solution](http://jsfiddle.net/mekwall/fNyHs/). sandstorm's method is most likely more accurate but this one is faster ;) – mekwall Nov 14 '11 at 15:43
  • 2
    Here is a version with minimum font size as well as maximum: https://gist.github.com/1714284 – Jess Telford Feb 01 '12 at 01:07
  • What if text within `div` is going to change dynamically - will it be recalculated automatically? If no, what is needed to make it work? – Ωmega Oct 08 '12 at 15:36
  • @Ωmega Just run it again after it has changed. – mekwall Oct 08 '12 at 16:42
  • @MarcusEkwall check my answer, it is orders of magnitude faster. I used a mathematical approach to calculate the font-size so I don't need to call .css("font-size") inside a loop. – Hoffmann Jul 15 '13 at 15:47
  • 1
    @Hoffmann Hmm. My solution does not call `.css("font-size")` in a loop. Where did you get that from? My solution is probably faster since it doesn't have any of the fancy stuff you added to your plugin. You're welcome to add your plugin to the jsperf and we'll see which one is the fastest ;) – mekwall Jul 15 '13 at 19:56
  • 1
    @MarcusEkwall Oh sorry, for some reason I though I saw a while loop there. Your approach is similar to my own and indeed my would be a bit slower since my plugin also does some other stuff (like adjusting to fit both height and width, centralizes the text and some other options), not that matters, the really slow part is calling the .css function inside a loop. – Hoffmann Jul 16 '13 at 02:12
  • This is a good answer. I forked that Gist and made https://gist.github.com/andrewbranch/6995056, which doesn't require that span tag. – Andrew Oct 15 '13 at 17:20
  • I had several issues with this design, because I registered the function to be called when the window is resized. After rapidly resizing, the text pisses off to zero size. – Boom Feb 15 '16 at 23:14
  • @Boom You're welcome to improve it and/or fix any bugs :) – mekwall Feb 26 '16 at 09:03
38

As much as I love the occasional upvotes I get for this answer (thanks!), this is really not the greatest approach to this problem. Please check out some of the other wonderful answers here, especially the ones that have found solutions without looping.


Still, for the sake of reference, here's my original answer:

<html>
<head>
<style type="text/css">
    #dynamicDiv
    {
    background: #CCCCCC;
    width: 300px;
    height: 100px;
    font-size: 64px;
    overflow: hidden;
    }
</style>

<script type="text/javascript">
    function shrink()
    {
        var textSpan = document.getElementById("dynamicSpan");
        var textDiv = document.getElementById("dynamicDiv");

        textSpan.style.fontSize = 64;

        while(textSpan.offsetHeight > textDiv.offsetHeight)
        {
            textSpan.style.fontSize = parseInt(textSpan.style.fontSize) - 1;
        }
    }
</script>

</head>
<body onload="shrink()">
    <div id="dynamicDiv"><span id="dynamicSpan">DYNAMIC FONT</span></div>
</body>
</html>

And here's a version with classes:

<html>
<head>
<style type="text/css">
.dynamicDiv
{
    background: #CCCCCC;
    width: 300px;
    height: 100px;
    font-size: 64px;
    overflow: hidden;
}
</style>

<script type="text/javascript">
    function shrink()
    {
        var textDivs = document.getElementsByClassName("dynamicDiv");
        var textDivsLength = textDivs.length;

        // Loop through all of the dynamic divs on the page
        for(var i=0; i<textDivsLength; i++) {

            var textDiv = textDivs[i];

            // Loop through all of the dynamic spans within the div
            var textSpan = textDiv.getElementsByClassName("dynamicSpan")[0];

            // Use the same looping logic as before
            textSpan.style.fontSize = 64;

            while(textSpan.offsetHeight > textDiv.offsetHeight)
            {
                textSpan.style.fontSize = parseInt(textSpan.style.fontSize) - 1;
            }

        }

    }
</script>

</head>
<body onload="shrink()">
    <div class="dynamicDiv"><span class="dynamicSpan">DYNAMIC FONT</span></div>
    <div class="dynamicDiv"><span class="dynamicSpan">ANOTHER DYNAMIC FONT</span></div>
    <div class="dynamicDiv"><span class="dynamicSpan">AND YET ANOTHER DYNAMIC FONT</span></div>
</body>
</html>
attack
  • 1,523
  • 12
  • 17
32

Most of the other answers use a loop to reduce the font-size until it fits on the div, this is VERY slow since the page needs to re-render the element each time the font changes size. I eventually had to write my own algorithm to make it perform in a way that allowed me to update its contents periodically without freezing the user browser. I added some other functionality (rotating text, adding padding) and packaged it as a jQuery plugin, you can get it at:

https://github.com/DanielHoffmann/jquery-bigtext

simply call

$("#text").bigText();

and it will fit nicely on your container.

See it in action here:

http://danielhoffmann.github.io/jquery-bigtext/

For now it has some limitations, the div must have a fixed height and width and it does not support wrapping text into multiple lines.

I will work on getting an option to set the maximum font-size.

Edit: I have found some more problems with the plugin, it does not handle other box-model besides the standard one and the div can't have margins or borders. I will work on it.

Edit2: I have now fixed those problems and limitations and added more options. You can set maximum font-size and you can also choose to limit the font-size using either width, height or both. I will work into accepting a max-width and max-height values in the wrapper element.

Edit3: I have updated the plugin to version 1.2.0. Major cleanup on the code and new options (verticalAlign, horizontalAlign, textAlign) and support for inner elements inside the span tag (like line-breaks or font-awesome icons.)

Aardvark
  • 8,474
  • 7
  • 46
  • 64
Hoffmann
  • 14,369
  • 16
  • 76
  • 91
  • 1
    I am wondering why wrapping text into multiple line not supported? – Manish Sapariya May 19 '14 at 11:59
  • 1
    @ManishSapariya It is supported, but you need to add the line breaks (br tags) manually. The reason I don't support automatic text wrap is because to make it fast (only change the font-size twice instead of multiple times) I need to make the assumption that the text will not wrap between words. The way my plugin works is to set the font-size to 1000px then see the factor of the size the text is compared to the container, I then reduce the font-size by the same factor. To support normal text wrapping I would need to use the slow approach (reduce the font-size multiple times) which is very slow. – Hoffmann May 19 '14 at 13:22
  • Hey! Since there is no private messaging here, on StackOverflow, I will have to ask you by commenting on your answer. I love your jQuery plugin, but I can't get it to work for me. I've included the right jQuery library, downloaded your plugin and included it. Now when I try and use it, the console says 'Uncaught TypeError: undefined is not a function'. Is this something you are familiar with? Do you know how to fix this? Thanks – Gust van de Wal Oct 28 '14 at 01:01
  • @GustvandeWal You need to include the plugin after including the jquery library – Hoffmann Oct 28 '14 at 12:18
  • I did. I've got The browser doesn't notify me that it can't load the jQuery library or the plugin. – Gust van de Wal Oct 28 '14 at 22:41
  • @GustvandeWal please create an issue on github with the complete stack trace and preferably a jsfiddle.net example. – Hoffmann Oct 29 '14 at 12:54
9

This is based on what GeekyMonkey posted above, with some modifications.

; (function($) {
/**
* Resize inner element to fit the outer element
* @author Some modifications by Sandstrom
* @author Code based on earlier works by Russ Painter (WebDesign@GeekyMonkey.com)
* @version 0.2
*/
$.fn.textfill = function(options) {

    options = jQuery.extend({
        maxFontSize: null,
        minFontSize: 8,
        step: 1
    }, options);

    return this.each(function() {

        var innerElements = $(this).children(':visible'),
            fontSize = options.maxFontSize || innerElements.css("font-size"), // use current font-size by default
            maxHeight = $(this).height(),
            maxWidth = $(this).width(),
            innerHeight,
            innerWidth;

        do {

            innerElements.css('font-size', fontSize);

            // use the combined height of all children, eg. multiple <p> elements.
            innerHeight = $.map(innerElements, function(e) {
                return $(e).outerHeight();
            }).reduce(function(p, c) {
                return p + c;
            }, 0);

            innerWidth = innerElements.outerWidth(); // assumes that all inner elements have the same width
            fontSize = fontSize - options.step;

        } while ((innerHeight > maxHeight || innerWidth > maxWidth) && fontSize > options.minFontSize);

    });

};

})(jQuery);
sandstrom
  • 14,554
  • 7
  • 65
  • 62
  • difference is that it can take multiple child elements, and that it takes padding into account. Uses font-size as the default maximum size, to avoid mixing javascript and css. – sandstrom Nov 08 '09 at 14:59
  • 5
    This is great, but how do I use it? I do $('.outer').textfill(); and I get no change. – Drew Baker Mar 13 '11 at 21:21
  • 3
    Thanks, this is a very nice implementation. One thing I ran into: if you are dealing with very long text strings and very narrow containers, the text string will stick out of the container, but the outerWidth will still be calculated as if it doesn't. Toss "word-wrap: break-word;" into your CSS for that container, it will fix this issue. – Jon Nov 05 '11 at 01:45
8

Here's an improved looping method that uses binary search to find the largest possible size that fits into the parent in the fewest steps possible (this is faster and more accurate than stepping by a fixed font size). The code is also optimized in several ways for performance.

By default, 10 binary search steps will be performed, which will get within 0.1% of the optimal size. You could instead set numIter to some value N to get within 1/2^N of the optimal size.

Call it with a CSS selector, e.g.: fitToParent('.title-span');

/**
 * Fit all elements matching a given CSS selector to their parent elements'
 * width and height, by adjusting the font-size attribute to be as large as
 * possible. Uses binary search.
 */
var fitToParent = function(selector) {
    var numIter = 10;  // Number of binary search iterations
    var regexp = /\d+(\.\d+)?/;
    var fontSize = function(elem) {
        var match = elem.css('font-size').match(regexp);
        var size = match == null ? 16 : parseFloat(match[0]);
        return isNaN(size) ? 16 : size;
    }
    $(selector).each(function() {
        var elem = $(this);
        var parentWidth = elem.parent().width();
        var parentHeight = elem.parent().height();
        if (elem.width() > parentWidth || elem.height() > parentHeight) {
            var maxSize = fontSize(elem), minSize = 0.1;
            for (var i = 0; i < numIter; i++) {
                var currSize = (minSize + maxSize) / 2;
                elem.css('font-size', currSize);
                if (elem.width() > parentWidth || elem.height() > parentHeight) {
                    maxSize = currSize;
                } else {
                    minSize = currSize;
                }
            }
            elem.css('font-size', minSize);
        }
    });
};
Luke Hutchison
  • 8,186
  • 2
  • 45
  • 40
  • Love this option. I modified it to add parameters for `vAlign` and `padding`. `vAlign == true` sets the line-height of the selected element to that of the parents height. Padding decreases the final size by the passed value. It defaults to 5. I think it looks really nice. –  Oct 04 '17 at 15:43
6

I've created a directive for AngularJS - heavely inspired by GeekyMonkey's answer but without the jQuery dependency.

Demo: http://plnkr.co/edit/8tPCZIjvO3VSApSeTtYr?p=preview

Markup

<div class="fittext" max-font-size="50" text="Your text goes here..."></div>

Directive

app.directive('fittext', function() {

  return {
    scope: {
      minFontSize: '@',
      maxFontSize: '@',
      text: '='
    },
    restrict: 'C',
    transclude: true,
    template: '<div ng-transclude class="textContainer" ng-bind="text"></div>',
    controller: function($scope, $element, $attrs) {
      var fontSize = $scope.maxFontSize || 50;
      var minFontSize = $scope.minFontSize || 8;

      // text container
      var textContainer = $element[0].querySelector('.textContainer');

      angular.element(textContainer).css('word-wrap', 'break-word');

      // max dimensions for text container
      var maxHeight = $element[0].offsetHeight;
      var maxWidth = $element[0].offsetWidth;

      var textContainerHeight;
      var textContainerWidth;      

      var resizeText = function(){
        do {
          // set new font size and determine resulting dimensions
          textContainer.style.fontSize = fontSize + 'px';
          textContainerHeight = textContainer.offsetHeight;
          textContainerWidth = textContainer.offsetWidth;

          // shrink font size
          var ratioHeight = Math.floor(textContainerHeight / maxHeight);
          var ratioWidth = Math.floor(textContainerWidth / maxWidth);
          var shrinkFactor = ratioHeight > ratioWidth ? ratioHeight : ratioWidth;
          fontSize -= shrinkFactor;

        } while ((textContainerHeight > maxHeight || textContainerWidth > maxWidth) && fontSize > minFontSize);        
      };

      // watch for changes to text
      $scope.$watch('text', function(newText, oldText){
        if(newText === undefined) return;

        // text was deleted
        if(oldText !== undefined && newText.length < oldText.length){
          fontSize = $scope.maxFontSize;
        }
        resizeText();
      });
    }
  };
});
sqren
  • 22,833
  • 7
  • 52
  • 36
  • One problem I've been having with this is that `resizeText` seems to get called before the `ng-bind` actually assigns the text to the element, resulting in it sizing based on the previous text rather than the current text. This isn't too bad in the demo above where it's called repeatedly as the user types, but if it gets called once going from null to the real value (as in a one-way binding) it stays at the max size. – Miral Oct 14 '14 at 23:36
5

I forked the script above from Marcus Ekwall: https://gist.github.com/3945316 and tweaked it to my preferences, it now fires when the window is resized, so that the child always fits its container. I've pasted the script below for reference.

(function($) {
    $.fn.textfill = function(maxFontSize) {
        maxFontSize = parseInt(maxFontSize, 10);
        return this.each(function(){
            var ourText = $("span", this);
            function resizefont(){
                var parent = ourText.parent(),
                maxHeight = parent.height(),
                maxWidth = parent.width(),
                fontSize = parseInt(ourText.css("fontSize"), 10),
                multiplier = maxWidth/ourText.width(),
                newSize = (fontSize*(multiplier));
                ourText.css("fontSize", maxFontSize > 0 && newSize > maxFontSize ? maxFontSize : newSize );
            }
            $(window).resize(function(){
                resizefont();
            });
            resizefont();
        });
    };
})(jQuery);
Julian Krispel-Samsel
  • 7,512
  • 3
  • 33
  • 40
  • 2
    It is great that you are trying to help the asker out. However, leaving an answer with only a link can be harmful in some cases. While your answer is good now, if the link were ever to die, your answer would lose it's value. So it will be helpful if you summarize the content from the article in your answer. See [this](http://goo.gl/wQTjc) question for clarification. – Cody Guldner Jul 02 '13 at 20:17
5

Here's my modification of the OP's answer.

In short, many people who tried to optimize this complained that a loop was being used. Yes, while loops can be slow, other approaches can be inaccurate.

Therefore, my approach uses Binary Search to find the best Font Size:

$.fn.textfill = function()
{
    var self = $(this);
    var parent = self.parent();

    var attr = self.attr('max-font-size');
    var maxFontSize = parseInt(attr, 10);
    var unit = attr.replace(maxFontSize, "");

    var minFontSize = parseInt(self.attr('min-font-size').replace(unit, ""));
    var fontSize = (maxFontSize + minFontSize) / 2;

    var maxHeight = parent.height();
    var maxWidth = parent.width();

    var textHeight;
    var textWidth;

    do
    {
        self.css('font-size', fontSize + unit);

        textHeight = self.height();
        textWidth = self.width();

        if(textHeight > maxHeight || textWidth > maxWidth)
        {
            maxFontSize = fontSize;
            fontSize = Math.floor((fontSize + minFontSize) / 2);
        }
        else if(textHeight < maxHeight || textWidth < maxWidth)
        {
            minFontSize = fontSize;
            fontSize = Math.floor((fontSize + maxFontSize) / 2);
        }
        else
            break;

    }
    while(maxFontSize - minFontSize > 1 && maxFontSize > minFontSize);

    self.css('font-size', fontSize + unit);

    return this;
}

function resizeText()
{
  $(".textfill").textfill();
}

$(document).ready(resizeText);
$(window).resize(resizeText);

This also allows the element to specify the minimum and maximum font:

<div class="container">
    <div class="textfill" min-font-size="10px" max-font-size="72px">
        Text that will fill the container, to the best of its abilities, and it will <i>never</i> have overflow.
    </div>
</div>

Furthermore, this algorithm is unitless. You may specify em, rem, %, etc. and it will use that for its final result.

Here's the Fiddle: https://jsfiddle.net/fkhqhnqe/1/

Boom
  • 2,465
  • 4
  • 19
  • 21
2

The proposed iterative solutions can be sped up dramatically on two fronts:

1) Multiply the font size by some constant, rather than adding or subtracting 1.

2) First, zero in using a course constant, say, double the size each loop. Then, with a rough idea of where to start, do the same thing with a finer adjustment, say, multiply by 1.1. While the perfectionist might want the exact integer pixel size of the ideal font, most observers don't notice the difference between 100 and 110 pixels. If you are a perfectionist, then repeat a third time with an even finer adjustment.

Rather than writing a specific routine or plug-in that answers the exact question, I just rely on the basic ideas and write variations of the code to handle all kinds of layout issues, not just text, including fitting divs, spans, images,... by width, height, area,... within a container, matching another element....

Here's an example:

  var                           nWindowH_px             = jQuery(window).height();
  var                           nWas                    = 0;
  var                           nTry                    = 5;

  do{
   nWas = nTry;
   nTry *= 2;
   jQuery('#divTitle').css('font-size' ,nTry +'px');
  }while( jQuery('#divTitle').height() < nWindowH_px );

  nTry = nWas;

  do{
   nWas = nTry;
   nTry = Math.floor( nTry * 1.1 );
   jQuery('#divTitle').css('font-size' ,nTry +'px');
  }while( nWas != nTry   &&   jQuery('#divTitle').height() < nWindowH_px );

  jQuery('#divTitle').css('font-size' ,nWas +'px');
FelixSFD
  • 6,052
  • 10
  • 43
  • 117
DaveWalley
  • 817
  • 10
  • 22
  • Great idea @DaveWalley! Multiplication indeed should perform faster. But me oh my what kind of style is this. It looks like assembly. Cheers Dave! – Nick Gallimore Sep 27 '22 at 08:35
2

I had exactly the same problem with my website. I have a page that is displayed on a projector, on walls, big screens..

As I don't know the max size of my font, I re-used the plugin above of @GeekMonkey but incrementing the fontsize :

$.fn.textfill = function(options) {
        var defaults = { innerTag: 'span', padding: '10' };
        var Opts = jQuery.extend(defaults, options);

        return this.each(function() {
            var ourText = $(Opts.innerTag + ':visible:first', this);
            var fontSize = parseFloat(ourText.css('font-size'),10);
            var doNotTrepass = $(this).height()-2*Opts.padding ;
            var textHeight;

            do {
                ourText.css('font-size', fontSize);
                textHeight = ourText.height();
                fontSize = fontSize + 2;
            } while (textHeight < doNotTrepass );
        });
    };
guillaumepotier
  • 7,369
  • 8
  • 45
  • 72
1

Here is a version of the accepted answer which can also take a minFontSize parameter.

(function($) {
    /**
    * Resizes an inner element's font so that the inner element completely fills the outer element.
    * @author Russ Painter WebDesign@GeekyMonkey.com
    * @author Blake Robertson 
    * @version 0.2 -- Modified it so a min font parameter can be specified.
    *    
    * @param {Object} Options which are maxFontPixels (default=40), innerTag (default='span')
    * @return All outer elements processed
    * @example <div class='mybigdiv filltext'><span>My Text To Resize</span></div>
    */
    $.fn.textfill = function(options) {
        var defaults = {
            maxFontPixels: 40,
            minFontPixels: 10,
            innerTag: 'span'
        };
        var Opts = jQuery.extend(defaults, options);
        return this.each(function() {
            var fontSize = Opts.maxFontPixels;
            var ourText = $(Opts.innerTag + ':visible:first', this);
            var maxHeight = $(this).height();
            var maxWidth = $(this).width();
            var textHeight;
            var textWidth;
            do {
                ourText.css('font-size', fontSize);
                textHeight = ourText.height();
                textWidth = ourText.width();
                fontSize = fontSize - 1;
            } while ((textHeight > maxHeight || textWidth > maxWidth) && fontSize > Opts.minFontPixels);
        });
    };
})(jQuery);
blak3r
  • 16,066
  • 16
  • 78
  • 98
1

You can use FitText.js (github page) to solve this problem. Is really small and efficient compared to TextFill. TextFill uses an expensive while loop and FitText don't.

Also FitText is more flexible (I use it in a proyect with very special requirements and works like a champ!).

HTML:

<div class="container">
  <h1 id="responsive_headline">Your fancy title</h1>
</div>

<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script src="jquery.fittext.js"></script>
<script>
  jQuery("#responsive_headline").fitText();
</script>

You also can set options to it:

<script>
  jQuery("#responsive_headline").fitText(1, { minFontSize: '30px', maxFontSize: '90px'});
</script>

CSS:

#responsive_headline {
   width: 100%;
   display: block;
}

And if you need it, FitText also has a no-jQuery version.

eduludi
  • 1,618
  • 21
  • 23
1

EDIT: This code was used to show notes on top of a HTML5 video. It changes the font-size on the fly when the video is resized (when the browser window is resized.) The notes was connected to the video (just like notes on YouTube), which is why the code uses instances instead of a DOM handle directly.

As per request I'll throw in some code that I used to achieve this. (Text boxes over an HTML5 video.) The code was written a long time ago, and I quite frankly think it's pretty messy. Since the question is already answered and an answer is already accepted a long time ago I don't bother rewriting this. But if anyone wants to simplify this a bit, you're more than welcome!

// Figure out the text size:
var text = val['text'];
var letters = text.length;
var findMultiplier = function(x) { // g(x)
    /* By analysing some functions with regression, the resulting function that
     gives the best font size with respect to the number of letters and the size
     of the note is:
     g(x) = 8.3 - 2.75x^0.15 [1 < x < 255]
     f(x) = g(letters) * (x / 1000)^0.5
     Font size = f(size)
     */
    return 8.3 - 2.75 * Math.pow(x, 0.15);
};

var findFontSize = function(x) { // f(x)
    return findMultiplier(letters) * Math.pow(x / 1000, 0.5);
};

val.setFontSizeListener = function() {
    p.style.fontSize = '1px'; // So the text should not overflow the box when measuring.
    var noteStyle = window.getComputedStyle(table);
    var width = noteStyle.getPropertyValue('width');
    var height = noteStyle.getPropertyValue('height');
    var size = width.substring(0, width.length - 2) * height.substring(0, height.length - 2);
    p.style.fontSize = findFontSize(size) + 'px';
};
window.addEventListener('resize', val.setFontSizeListener);

You'll probably need to tweak these numbers from font-family to font-family. A good way to do this is download a free graph visualizer called GeoGebra. Change the length of the text and the size of the box. Then you manually set the size. Plot the manual results into the coordinate system. Then you enter the two equations I've posted here and you tweak the numbers until "my" graph fits your own manually plotted points.

Friend of Kim
  • 850
  • 9
  • 24
1

Just wanted to add my version for contenteditables.

$.fn.fitInText = function() {
  this.each(function() {

    let textbox = $(this);
    let textboxNode = this;

    let mutationCallback = function(mutationsList, observer) {
      if (observer) {
        observer.disconnect();
      }
      textbox.css('font-size', 0);
      let desiredHeight = textbox.css('height');
      for (i = 12; i < 50; i++) {
        textbox.css('font-size', i);
        if (textbox.css('height') > desiredHeight) {
          textbox.css('font-size', i - 1);
          break;
        }
      }

      var config = {
        attributes: true,
        childList: true,
        subtree: true,
        characterData: true
      };
      let newobserver = new MutationObserver(mutationCallback);
      newobserver.observe(textboxNode, config);

    };

    mutationCallback();

  });
}

$('#inner').fitInText();
#outer {
  display: table;
  width: 100%;
}

#inner {
  border: 1px solid black;
  height: 170px;
  text-align: center;
  display: table-cell;
  vertical-align: middle;
  word-break: break-all;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="outer">
  <div id="inner" contenteditable=true>
    TEST
  </div>
</div>
wutzebaer
  • 14,365
  • 19
  • 99
  • 170
  • is exactly what I need. I've honestly never used jquery, is it possible to do it with pure JavaScript? I want to implement this in a web page with Angular. – JulianProg Oct 31 '20 at 23:18
1

This uses binary search, doing 10 iterations. The naive way was to do a while loop and increase the font size by 1 until the element started to overflow. You can determine when an element begins to overflow using element.offsetHeight and element.scrollHeight. If scrollHeight is bigger than offsetHeight, you have a font size that is too big.

Binary search is a much better algorithm for this. It also is limited by the number of iterations you want to perform. Simply call flexFont and insert the div id and it will adjust the font size between 8px and 96px.

I have spent some time researching this topic and trying different libraries, but ultimately I think this is the easiest and most straightforward solution that will actually work.

Note if you want you can change to use offsetWidth and scrollWidth, or add both to this function.

    // Set the font size using overflow property and div height
    function flexFont(divId) {
        var content = document.getElementById(divId);
        content.style.fontSize = determineMaxFontSize(content, 8, 96, 10, 0) + "px";
    };

    // Use binary search to determine font size
    function determineMaxFontSize(content, min, max, iterations, lastSizeNotTooBig) {
        if (iterations === 0) {
            return lastSizeNotTooBig;
        }
        var obj = fontSizeTooBig(content, min, lastSizeNotTooBig);

        // if `min` too big {....min.....max.....}
        // search between (avg(min, lastSizeTooSmall)), min)
        // if `min` too small, search between (avg(min,max), max)
        // keep track of iterations, and the last font size that was not too big
        if (obj.tooBig) {
            (lastSizeTooSmall === -1) ?
                determineMaxFontSize(content, min / 2, min, iterations - 1, obj.lastSizeNotTooBig, lastSizeTooSmall) :
                    determineMaxFontSize(content, (min + lastSizeTooSmall) / 2, min, iterations - 1, obj.lastSizeNotTooBig, lastSizeTooSmall);
            
        } else {
            determineMaxFontSize(content, (min + max) / 2, max, iterations - 1, obj.lastSizeNotTooBig, min);
        }
    }

    // determine if fontSize is too big based on scrollHeight and offsetHeight, 
    // keep track of last value that did not overflow
    function fontSizeTooBig(content, fontSize, lastSizeNotTooBig) {
        content.style.fontSize = fontSize + "px";
        var tooBig = content.scrollHeight > content.offsetHeight;
        return {
            tooBig: tooBig,
            lastSizeNotTooBig: tooBig ? lastSizeNotTooBig : fontSize
        };
    }
Nick Gallimore
  • 1,222
  • 13
  • 31
  • 4
    Thanks, this looks great! I'm just getting `ReferenceError: lastSizeTooSmall is not defined`. Maybe that needs to be defined somewhere? – ndbroadbent May 17 '20 at 19:53
  • 1
    Unfortunately, aside from that, this script optimizes the wrong thing. Loops aren't everything when the bounds are this small, ternaries are for getting return values, creating objects also comes with a cost, and readability matters. Nice idea to use binary search, but... not great when the code doesn't even work! I intended to fix the code, but it's unclear to me what that value should even be after looking through how it works. – Kyle Baker Sep 23 '22 at 01:02
  • 1
    @KyleBaker I 100% agree. The code should run and does not, however I still think this answer is useful for the text content. For me it was necessary to use binary search as this was called a lot. Sorry, this code deserves a spot in hell and should reside there. Ironically some version of this is still in production at my company, and was unable to be replaced. This 100% is a massive headache, but I like the idea of using Binary search as well. Also the other main part is the syntax for getting element.offsetHeight and element.scrollWidth. Thanks for the feedback Kyle! – Nick Gallimore Sep 27 '22 at 08:32
0

I went with geekMonkey solution, but it's too slow. What he does, is he adjusts the font size to maximum (maxFontPixels) and then checks if it fits inside the container. else it reduces the font size by 1px and checks again. Why not simply check the previous container for the height and submit that value? (yes, I know why, but I now made a solution, that only works on the height and also has a min/max option)

Here is a much quicker solution:

var index_letters_resize;
(index_letters_resize = function() {
  $(".textfill").each(function() {
    var
      $this = $(this),
      height = Math.min( Math.max( parseInt( $this.height() ), 40 ), 150 );
    $this.find(".size-adjust").css({
      fontSize: height
    });
  });
}).call();

$(window).on('resize', function() {
  index_letters_resize();
);

and this would be the HTML:

<div class="textfill">
  <span class="size-adjust">adjusted element</span>
  other variable stuff that defines the container size
</div>

Again: this solution ONLY checks for the height of the container. That's why this function does not has to check, if the element fits inside. But I also implemented a min/max value (40min, 150max) so for me this works perfectly fine (and also works on window resize).

halfer
  • 19,824
  • 17
  • 99
  • 186
honk31
  • 3,895
  • 3
  • 31
  • 30
0

I got the same problem and the solution is basically use javascript to control font-size. Check this example on codepen:

https://codepen.io/ThePostModernPlatonic/pen/BZKzVR

This is example is only for height, maybe you need to put some if's about the width.

try to resize it

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Documento sem título</title>
<style>
</style>
</head>
<body>
<div style="height:100vh;background-color: tomato;" id="wrap">        
  <h1 class="quote" id="quotee" style="padding-top: 56px">Because too much "light" doesn't <em>illuminate</em> our paths and warm us, it only blinds and burns us.</h1>
</div>
</body>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script>
  var multiplexador = 3;
  initial_div_height = document.getElementById ("wrap").scrollHeight;
  setInterval(function(){ 
    var div = document.getElementById ("wrap");
    var frase = document.getElementById ("quotee");
    var message = "WIDTH div " + div.scrollWidth + "px. "+ frase.scrollWidth+"px. frase \n";
    message += "HEIGHT div " + initial_div_height + "px. "+ frase.scrollHeight+"px. frase \n";           
    if (frase.scrollHeight < initial_div_height - 30){
      multiplexador += 1;
      $("#quotee").css("font-size", multiplexador); 
    }
    console.log(message);          
  }, 10);
</script>
</html>
iSkore
  • 7,394
  • 3
  • 34
  • 59
Heitor Giacomini
  • 384
  • 2
  • 12
0

I did like

let name = "Making statements based on opinion; back them up with references or personal experience."
let originFontSize = 15;
let maxDisplayCharInLine = 50; 
let fontSize = Math.min(originFontSize, originFontSize / (name.length / maxDisplayCharInLine));
Tuan Nguyen
  • 2,542
  • 19
  • 29
0

I have found a way to prevent the use of loops to shrink the text. It adjusts the font-size by multiplying it for the rate between container's width and content width. So if the container's width is 1/3 of the content, the font-size will be reduced by 1/3 and will container's width. To scale up, I have used a while loop, until content is bigger than container.

function fitText(outputSelector){
    // max font size in pixels
    const maxFontSize = 50;
    // get the DOM output element by its selector
    let outputDiv = document.getElementById(outputSelector);
    // get element's width
    let width = outputDiv.clientWidth;
    // get content's width
    let contentWidth = outputDiv.scrollWidth;
    // get fontSize
    let fontSize = parseInt(window.getComputedStyle(outputDiv, null).getPropertyValue('font-size'),10);
    // if content's width is bigger than elements width - overflow
    if (contentWidth > width){
        fontSize = Math.ceil(fontSize * width/contentWidth,10);
        fontSize =  fontSize > maxFontSize  ? fontSize = maxFontSize  : fontSize - 1;
        outputDiv.style.fontSize = fontSize+'px';   
    }else{
        // content is smaller than width... let's resize in 1 px until it fits 
        while (contentWidth === width && fontSize < maxFontSize){
            fontSize = Math.ceil(fontSize) + 1;
            fontSize = fontSize > maxFontSize  ? fontSize = maxFontSize  : fontSize;
            outputDiv.style.fontSize = fontSize+'px';   
            // update widths
            width = outputDiv.clientWidth;
            contentWidth = outputDiv.scrollWidth;
            if (contentWidth > width){
                outputDiv.style.fontSize = fontSize-1+'px'; 
            }
        }
    }
}

This code is part of a test that I have uploaded to Github https://github.com/ricardobrg/fitText/

Ricardo Gonçalves
  • 4,344
  • 2
  • 20
  • 30
-1

Here is another version of this solution:

shrinkTextInElement : function(el, minFontSizePx) {
    if(!minFontSizePx) {
        minFontSizePx = 5;
    }
    while(el.offsetWidth > el.parentNode.offsetWidth || el.offsetHeight > el.parentNode.offsetHeight) {

        var newFontSize = (parseInt(el.style.fontSize, 10) - 3);
        if(newFontSize <= minFontSizePx) {
            break;
        }

        el.style.fontSize = newFontSize + "px";
    }
}
PaulG
  • 154
  • 5