[jquery] resize font to fit in a div (on one line)

There have been similar questions asked, but the solutions do mesh with what I'm trying to do. Basically, I have an article with a title (<h1>). I don't want to control the length of the title, but I also don't want the title to appear on multiple lines. Is there a way with css or jQuery to resize text based on the width of a <div> tag?

I know what I would do with pseudocode if I could detect the overlap of the text to the edge of the <div>:

var fontize = $("#title").css("font-size");
var i = /*remove unit from integer*/
while( /*text overlaps div*/ ){
    $("#title").css("font-size", --i+"pt");
}

If there's a CSS attribute I can set that would be even nicer, but I can't seem to find one (overflow wouldn't work in this situation).

This question is related to jquery css

The answer is


@Clovis Six thank for your answer. It prove very usefull to me. A pity I cannot thanks you more than just a vote up.

Note: I have to change the "$J(" for "$(" for it to work on my config.

evendo, this out of the scope of this question and not in the use of SO, I extended your code to make it work for multi-line box with max-height.

  /**
   * Adjust the font-size of the text so it fits the container
   *
   * support multi-line, based on css 'max-height'.
   * 
   * @param minSize     Minimum font size?
   * @param maxSize     Maximum font size?
   */
  $.fn.autoTextSize_UseMaxHeight = function(minSize, maxSize) {
    var _self = this,
        _width = _self.innerWidth(),
        _boxHeight = parseInt(_self.css('max-height')),
        _textHeight = parseInt(_self.getTextHeight(_width)),
        _fontSize = parseInt(_self.css('font-size'));

    while (_boxHeight < _textHeight || (maxSize && _fontSize > parseInt(maxSize))) {
      if (minSize && _fontSize <= parseInt(minSize)) break;

      _fontSize--;
      _self.css('font-size', _fontSize + 'px');

      _textHeight = parseInt(_self.getTextHeight(_width));
    }
  };

PS: I know this should be a comment, but comments don't let me post code properly.


There is a jquery plugin available on github that probably just do what you want. It is called jquery-quickfit. It uses Jquery to provide a quick and dirty approach to fitting text into its surrounding container.

HTML:

<div id="quickfit">Text to fit*</div>

Javascript:

<script src=".../jquery.min.js" type="text/javascript" />
<script src="../script/jquery.quickfit.js" type="text/javascript" />

<script type="text/javascript">
  $(function() {
    $('#quickfit').quickfit();
  });
</script>

More information: https://github.com/chunksnbits/jquery-quickfit


I understand that this question seems to have been answered fairly thoroughly, but there were some instances where solutions here would may cause other issues. For example, tkahn's library looked to be very useful, but it changed the display of the element it was attached to, which could prove to be a problem. In my case, it prevented me from centering the text both vertically and horizontally. After some messing around and experimenting, I have come up with a simple method involving jQuery to fit the text on one line without needing to modify any attributes of the parent element. Note that in this code, I have used Robert Koritnik's suggestion for optimizing the while loop. To use this code, simply add the "font_fix" class to any divs containing text needing to be fit to it in one line. For a header, this may require an extra div around the header. Then, either call this function once for a fixed size div, or set it to a resize and/or orientation listener for varying sizes.

function fixFontSize(minimumSize){
  var x = document.getElementsByClassName("font_fix");
  for(var i = 0; i < x.length; i++){
    x[i].innerHTML = '<div class="font_fix_inner" style="white-space: nowrap; display: inline-block;">' + x[i].innerHTML + '</div>';

    var y = x[i].getElementsByClassName("font_fix_inner")[0];
    var size = parseInt($("#" + x[i].id).css("font-size"));

    size *= x[i].clientWidth / y.clientWidth;
    while(y.clientWidth > x[i].clientWidth){
      size--;
      if(size <= minimumSize){
        size = minimumSize;
        y.style.maxWidth = "100%";
        y.style.overflow = "hidden";
        y.style.textOverflow = "ellipsis";
        $("#" + x[i].id).css("font-size", size + "px");
        break;
      }
      $("#" + x[i].id).css("font-size", size + "px");
    }
  }
}

Now, I've added an additional case where the text is chopped off if it gets too small (below the minimum threshold passed into the function) for convenience. Another thing that happens once that threshold is reached that may or may not be desired is the changing of the max width to 100%. This should be changed for each user's scenario. Finally, the whole purpose of posting this answer as an alternate is for its abilities to center the content within the parent div. That can be easily done by adding css attributes to the inner div class as follows:

.font_fix_inner {
  position: relative;
  float: center;
  top: 50%;
  -webkit-transform: translateY(-50%);
  transform: translateY(-50%);
}

Hope this helped someone!


I had a similar issue, which made me write my own plugin for this. Have a look at jquery-quickfit (which is quite similar to Robert Koritnik's solution, which I really like).

In order to prevent the headline to span multiple lines, just add a css style of:

white-space:nowrap;

to the element.

After including jquery and quickfit in the header. You can trigger quickfit with:

$('h1').quickfit();

It meassures and calculates a size invariant meassure for each letter of the text to fit and uses this to calculate the next best font-size which fits the text into the container.

The calculations are cached, which makes it very fast when dealing having to fit multiple text or having to fit a text multiple times, like e.g., on window resize (there is almost no performance penalty on resize).

Demo for a similar situation as yours

Further documentation, examples and source code are on the project page.


Today I created a jQuery plugin called jQuery Responsive Headlines that does exactly what you want: it adjusts the font size of a headline to fit inside its containing element (a div, the body or whatever). There are some nice options that you can set to customize the behavior of the plugin and I have also taken the precaution to add support for Ben Altmans Throttle/Debounce functionality to increase performance and ease the load on the browser/computer when the user resizes the window. In it's most simple use case the code would look like this:

$('h1').responsiveHeadlines();

...which would turn all h1 on the page elements into responsive one-line headlines that adjust their sizes to the parent/container element.

You'll find the source code and a longer description of the plugin on this GitHub page.

Good luck!


This may be overkill for what you require, but I found this library to be very helpful:

http://fittextjs.com/

It's only good for single lines though, so I'm not certain if that fits your requirement.


My solution, as a jQuery extension based on Robert Koritnik's answer:

$.fn.fitToWidth=function(){
    $(this).wrapInner("<span style='display:inline;font:inherit;white-space:inherit;'></span>").each(function(){
        var $t=$(this);
        var a=$t.outerWidth(),
            $s=$t.children("span"),
            f=parseFloat($t.css("font-size"));
        while($t.children("span").outerWidth() > a) $t.css("font-size",--f);
        $t.html($s.html());
    });
}

This actually creates a temporary span inheriting important properties, and compares the width difference. Granted, the while loop needs to be optimised (reduce by a percentage difference calculated between the two sizes).

Example usage:

$(function(){
    $("h1").fitToWidth();
});

Check out this example here http://codepen.io/LukeXF/pen/yOQWNb, you can use a simple function on page load and page resize to make the magic happen. Though be careful not lag out the client from too many function calls. (The example has a delay).

function resize() {  
    $('.resize').each(function(i, obj) {
        $(this).css('font-size', '8em');

        while ($(this).width() > $(this).parent().width()) {
            $(this).css('font-size', (parseInt($(this).css('font-size')) - 1) + "px");
        }
    });
}

I wasn't in love with the other solutions out there, so, here's another one. It requires the tag you're resizing the text inside of:

  1. Be fixed height

  2. Not be so long that it'd overrun the boundaries at 10px - the idea being, you don't want it to shoot below that and resize text to become unreadable. Not an issue in our use case, but if it's possible in yours you'd want to give some thoughts to separately truncating before running this.

It uses a binary search to find the best size so I suspect it outperforms a lot of the other solutions here and elsewhere that just step the font-size down by a pixel over and over. Most browsers today support decimals in font sizes as well, and this script has some benefits there since it will get to within .1px of the best answer, whatever that is, even if it's a relatively long decimal. You could easily change the max - fontSize < .1 to some other value than .1 to get less precision for less CPU usage.

Usage:

$('div.event').fitText();

Code:

(function() {
    function resizeLoop(testTag, checkSize) {
        var fontSize = 10;
        var min = 10;
        var max = 0;
        var exceeded = false;

        for(var i = 0; i < 30; i++) {
            testTag.css('font-size', fontSize);
            if (checkSize(testTag)) {
                max = fontSize;
                fontSize = (fontSize + min) / 2;
            } else {
                if (max == 0) {
                    // Start by growing exponentially
                    min = fontSize;
                    fontSize *= 2;
                } else {
                    // If we're within .1px of max anyway, call it a day
                    if (max - fontSize < .1)
                        break;

                    // If we've seen a max, move half way to it
                    min = fontSize;
                    fontSize = (fontSize + max) / 2;
                }
            }
        }

        return fontSize;
    }

    function sizeText(tag) {
        var width = tag.width();
        var height = tag.height();

        // Clone original tag and append to the same place so we keep its original styles, especially font
        var testTag = tag.clone(true)
        .appendTo(tag.parent())
        .css({
            position: 'absolute',
            left: 0, top: 0,
            width: 'auto', height: 'auto'
        });

        var fontSize;

        // TODO: This decision of 10 characters is arbitrary. Come up
        // with a smarter decision basis.
        if (tag.text().length < 10) {
            fontSize = resizeLoop(testTag, function(t) {
                return t.width() > width || t.height() > height;
            });
        } else {
            testTag.css('width', width);
            fontSize = resizeLoop(testTag, function(t) {
                return t.height() > height;
            });
        }

        testTag.remove();
        tag.css('font-size', fontSize);
    };

    $.fn.fitText = function() {
        this.each(function(i, tag) {
            sizeText($(tag));
        });
    };
})();

http://jsfiddle.net/b9chris/et6N6/1/


I've make a very little jQuery stuff to adjust the font size of your div. It even works with multiple lines, but will only increase font's size (you can simply set a default font size of 1px if you want). I increases the font size until having a line break, set decreases it 1px. No other fancy stuff, hidden div or so. Simply add the quickfit class to your div.

$(".quickfit").each(function() {
    var jThis=$(this);
    var fontSize=parseInt(jThis.css("font-size"));
    var originalLines=jThis.height()/fontSize;

    for(var i=0;originalLines>=jThis.height()/fontSize && i<30;i++)
    { jThis.css("font-size",""+(++fontSize)+"px"); }
    jThis.css("font-size",""+(fontSize-1)+"px");
});

Here are 3 functions I use frequently to get the text width, height and adjust the size to the container's width.

  • getTextWidth() will return you the actual width of the text contained in the initiator.
  • getTextHeight(width) will return the actual height of the wrapped text contained in the initiator with a certain specified width.
  • autoTextSize(minSize, maxSize, truncate) will size the text within the container so it fits, considering a minimum and maximum size.
  • autoTruncateText() will only show the characters the user can actually see and end the text with '...'.
(function ($) {
  $.fn.getTextWidth = function() {
    var spanText = $("BODY #spanCalculateTextWidth");

    if (spanText.size() <= 0) {
      spanText = $("<span id='spanCalculateTextWidth' style='filter: alpha(0);'></span>");
      spanText.appendTo("BODY");
    }

    var valu = this.val();
    if (!valu) valu = this.text();

    spanText.text(valu);

    spanText.css({
      "fontSize": this.css('fontSize'),
      "fontWeight": this.css('fontWeight'),
      "fontFamily": this.css('fontFamily'),
      "position": "absolute",
      "top": 0,
      "opacity": 0,
      "left": -2000
    });

    return spanText.outerWidth() + parseInt(this.css('paddingLeft')) + 'px';
  };

  $.fn.getTextHeight = function(width) {
    var spanText = $("BODY #spanCalculateTextHeight");

    if (spanText.size() <= 0) {
      spanText = $("<span id='spanCalculateTextHeight'></span>");
      spanText.appendTo("BODY");
    }

    var valu = this.val();
    if (!valu) valu = this.text();

    spanText.text(valu);

    spanText.css({
      "fontSize": this.css('fontSize'),
      "fontWeight": this.css('fontWeight'),
      "fontFamily": this.css('fontFamily'),
      "top": 0,
      "left": -1 * parseInt(width) + 'px',
      "position": 'absolute',
      "display": "inline-block",
      "width": width
    });

    return spanText.innerHeight() + 'px';
  };

  /**
   * Adjust the font-size of the text so it fits the container.
   *
   * @param minSize     Minimum font size?
   * @param maxSize     Maximum font size?
   * @param truncate    Truncate text after sizing to make sure it fits?
   */
  $.fn.autoTextSize = function(minSize, maxSize, truncate) {
    var _self = this,
        _width = _self.innerWidth(),
        _textWidth = parseInt(_self.getTextWidth()),
        _fontSize = parseInt(_self.css('font-size'));

    while (_width < _textWidth || (maxSize && _fontSize > parseInt(maxSize))) {
      if (minSize && _fontSize <= parseInt(minSize)) break;

      _fontSize--;
      _self.css('font-size', _fontSize + 'px');

      _textWidth = parseInt(_self.getTextWidth());
    }

    if (truncate) _self.autoTruncateText();
  };

  /**
   * Function that truncates the text inside a container according to the
   * width and height of that container. In other words, makes it fit by chopping
   * off characters and adding '...'.
   */
  $.fn.autoTruncateText = function() {
    var _self = this,
        _width = _self.outerWidth(),
        _textHeight = parseInt(_self.getTextHeight(_width)),
        _text = _self.text();

    // As long as the height of the text is higher than that
    // of the container, we'll keep removing a character.
    while (_textHeight > _self.outerHeight()) {
      _text = _text.slice(0,-1);
      _self.text(_text);
      _textHeight = parseInt(_self.getTextHeight(_width));
      _truncated = true;
    }

    // When we actually truncated the text, we'll remove the last
    // 3 characters and replace it with '...'.
    if (!_truncated) return;
    _text = _text.slice(0, -3);

    // Make sure there is no dot or space right in front of '...'.
    var lastChar = _text[_text.length - 1];
    if (lastChar == ' ' || lastChar == '.') _text = _text.slice(0, -1);
    _self.text(_text + '...');
  };
})(jQuery);

You can accomplish this in css under certain circumstances, but it is brittle and context-dependent.

css font-sizes can accept relative sizes to the screen. IF the containing element for your header is sized relative to the screen size, you can accomplish this by simply setting font-size: 3vw or an appropriate size so that the text fits on one-line. If the screen is resized, the text will follow suit. Note that the width will depend on the exact text contents, and if you add characters to text that used to fit on a single line, you may need to adjust your font-size.

See this fiddle: https://jsfiddle.net/mguk8v27/


Very simple. Create a span for the text, get the width and reduce font-size until the span has the same width of the div container:

while($("#container").find("span").width() > $("#container").width()) {
    var currentFontSize = parseInt($("#container").find("span").css("font-size")); 
    $("#container").find("span").css("font-size",currentFontSize-1); 
}