Resize font depending on string length

16,516

Solution 1

You should make familiar with using plugins, they save you much time and of course they're very reliable (they are written by experienced scripters/programmers and have been tested by community). However looks like you want some pure JS solution. I've just made this code for you. It works fairly OK (although I'm not sure if it's as good as some plugins). The only requirement is the element (which you want to adjust the font-size accordingly to the text length) should contain plain text, not some HTML code.

The idea to implement it using pure JS is simple, you need some dummy element created using script, this dummy element is used to measure the size of the text. We need to adjust the font-size of the dummy element until the size of the text (as well as of the dummy element) should be confined to the size of the element (whose font-size you want to adjust). I made the code very clearly, hope you understand it better after reading the code:

//we just need 1 dummy element for the whole page.
var dummy = document.createElement('div');
dummy.className = 'dummy';
var inSingleLineMode, inMultilineMode;    
//function used to adjust the font-size of the element
//so that the width is fixed (single-line mode) or both the width and height are 
//fixed (multi-line mode), of course the text should be contained within 
//the fixed width and height.
function adjustFontSize(element, singleLine){
  if(!element.innerHTML) return;
  var elementStyle = getComputedStyle(element);        
  dummy.style.font = elementStyle.font;
  initMode(singleLine, function(){ dummy.style.width = elementStyle.width });
  dummy.style.padding = elementStyle.padding;
  dummy.style.boxSizing = elementStyle.boxSizing;
  dummy.innerHTML = element.innerHTML;
  document.body.appendChild(dummy);
  var dummyStyle = getComputedStyle(dummy);          
  while(singleLine ? parseInt(dummyStyle.width) < parseInt(elementStyle.width) :
                     parseInt(dummyStyle.height) < parseInt(elementStyle.height)){
    dummy.style.fontSize = parseFloat(dummyStyle.fontSize) + 1 + 'px';
    dummyStyle = getComputedStyle(dummy);
  }
  while(singleLine ? parseInt(dummyStyle.width) > parseInt(elementStyle.width) :
                     parseInt(dummyStyle.height) > parseInt(elementStyle.height)){
    dummy.style.fontSize = parseFloat(dummyStyle.fontSize) - 1 + 'px';
    dummyStyle = getComputedStyle(dummy);
  }    
  element.style.fontSize = dummyStyle.fontSize;
  document.body.removeChild(dummy);
}
function initMode(singleLine, callback){
  if(!dummy) return;
  if(singleLine&&!inSingleLineMode) {
    dummy.style.whiteSpace = 'nowrap';
    dummy.style.width = 'auto';
    dummy.style.display = "inline-block";
    inSingleLineMode = true;
    inMultiLineMode = false;
  } else if(!singleLine&&!inMultilineMode) {
    if(callback) callback();
    dummy.style.whiteSpace = 'initial';
    dummy.style.display = "block";
    dummy.style.wordWrap = 'break-word';
    inMultilineMode = true;
    inSingleLineMode = false;
  }
}

Demo.

In the demo, you can see that the first menu #menu1 is the Vietnamese word meaning Chrysanthemum while the second menu #menu2 is of course the English word Chrysanthemum. They have much different length, however both are supposed to have fixed width of 100px, hence the second menu #menu2 should have smaller font-size to fit the space.

Solution 2

You can use jQuery Text Fill like this.

  1. Load up the plugin: <script src="jquery.textfill.js" ></script>

  2. Put an id <input type="text" id="dyntext" value="e=mc²"></input>

  3. Use the code to do magic. Preferably put this in <script> tags:

The end result will look something like this:

 function update() {
  var size = parseInt($('#maxsize').val(), 10);
  if (!isNaN(size)) {
    $('.dyntextval').html($('#dyntext').val());
    $('.jtextfill').textfill({debug: true, maxFontPixels: size});
  }
}

$(function () {
  $('#maxsize').keyup(update);
  $('#dyntext').keyup(update);
  update()
});

enter image description here

Solution 3

I did look for that in the past then found an answer that really did the trick for me, but cannot remember were exactly... :( But since it does answer the question using pure javascript and no plugins/libraries (did some optimisations since though), here it is! (with a working example):

// First we add a new function to the String prototype,
// this will be used to get the length of current string according
// to its font-family and font-size
String.prototype.textWidth = function(fontFamily, fontSize) {
  var container = document.createElement('div');
  container.style.visibility = 'hidden';
  container.style.fontFamily = fontFamily;
  container.style.fontSize = fontSize + 'px';
  container.style.display = 'inline';
  document.body.appendChild(container);
  container.innerHTML = this;
  var pxLength = container.offsetWidth;
  container.parentNode.removeChild(container);
  return pxLength;
};

// this is the function that will resize our text if it's too long
// classNameTarget (String) = the className of the element we need to resize e a 
//    tag or an id but you'll need to make modification to this function!
// maxWidth (int) = the max width (in px) of your final string
// fontFamily (String) = the family currently used by your string(wrong one might lead
//    to wrong result!)
// fontSize (int) = the initial font-size of your string
var testWidth = function(classNameTarget, maxWidth, fontFamily, fontSize) {
    maxWidth = maxWidth || 100;
    // first we get all targets
    var containers = document.getElementsByClassName(classNameTarget);
    for (var i = 0; i < containers.length; i++) {
    // for each of them we fetch their current length
        var length = containers[i].innerHTML.textWidth(fontFamily, fontSize);
        if (length > maxWidth){
        // if the current length is bigger then we resize it by using a span styling with
        // the new font-size
            containers[i].innerHTML = "<span style=\"font-size:" + Math.floor(parseInt(fontSize) / (length / maxWidth)) + "px;\">" + containers[i].innerHTML + "</span>";
        }
    }
};

// we want our cell to have text no longer than 75px while having them starting at 50px 
// font-size in arial
testWidth("firstname", 75, "arial", 50);
testWidth("lastname", 75, "arial", 50);
<table>
  <thead>
    <tr>
      <th>firstname</th>
      <th>lastname</th>
    </tr>
  </thead>
  <tbody style="font-size: 50px; font-family: arial;">
    <tr>
      <td class="firstname">Bob</td>
      <td class="lastname">Tomysonorubia</td>
    </tr>
    <tr>
      <td class="firstname">John</td>
      <td class="lastname">Doe</td>
    </tr>
    <tr>
      <td class="firstname">François-Xavier</td>
      <td class="lastname">De la nouvelle Orléan</td>
    </tr>
  </tbody>
</table>

the testWidth method might need some adjustements regarding your currents needs, perhaps you'ld like to look into querySelector or querySelectorAll to make it quite generic

Solution 4

This is not possible without Javascript. Using Javascript, you can use one of the many libraries, like FitText.

So you could use a Javascript library for this, but that would also mean that various labels have different font sizes. I think the best approach would be to style the menu in such a way that it gracefully handles multi-line captions. That way, the length doesn't really matter much.

Because some language are 'longer' than others (for instance French labels are on avarage 1.5 to 2 times as long as English, it's a good idea to test your interface with one of those languages.

And for the font size, you could add a modifier on server side, for instace if you know the current language is French, you can add a class 'gui-captions-very-long' to the html tag and apply your CSS based on that class. That way, you can have a generic modifier which you can configure per language. I think that's a better solution than making all labels fit on a single line.

Keep in mind though, that smaller sizes are harder to read. You cannot just make the fonts half the size if the text is twice as long. You'll have to tune your design (or its implementation) to make longer texts possible.

Solution 5

I suggest an tiny example. On a pure JavaScript.

https://gist.github.com/dejurin/9bef02be6876e068ee276bee31cb3bcb

    "use strict";
    (function(w, d) {
        var fit = d.getElementById("fit");
        var wrap = d.getElementById("wrap");
        fontFitResize(fit, wrap);
        
            
    function fontFitResize(fit, wrap, step = 0.5) {
        var currentSize;
        while(fit.offsetWidth < wrap.offsetWidth) {
            currentSize = parseFloat(w.getComputedStyle(wrap, null).getPropertyValue('font-size'));
            wrap.style.fontSize = (currentSize - step) + 'px';
            console.log(wrap.style.fontSize);
        }
    }
        
    })(window, document);
.fit {
border: 1px solid #ff0000;
white-space:nowrap;
font-size:24px;
width:200px;
}
<div id="fit" class="fit">
  <span id="wrap">Resize font depending on string length</span>
</div>
Share:
16,516

Related videos on Youtube

RussianVodka
Author by

RussianVodka

PHP - 5 vodka bottles Java - 1 coffee cup

Updated on September 15, 2022

Comments

  • RussianVodka
    RussianVodka over 1 year

    I have a vertical menu and i want to make it localizable, but localized strings in menu elements often goes out off the edge.
    So the question is how to make font resizable depending on the string length in CSS. And if possible, without JavaScript.
    Thanks!

    UPD: JQuery isn't acceptable. Any way in Pure JS?

    • Ali Gajani
      Ali Gajani almost 10 years
      CSS can't figure out what the length of a string is.
  • RussianVodka
    RussianVodka almost 10 years
    Thanks! FitText is JQuery plugin, so i need JQuery to get it work. Any way in Pure JS?
  • Ali Gajani
    Ali Gajani almost 10 years
    Of course, but will take a while to code, jQuery does it for you.
  • GolezTrol
    GolezTrol almost 10 years
    Googling for 'FitText pure Javascript' leads me to -this question-. ;-)
  • RussianVodka
    RussianVodka almost 10 years
    Hmm... Thanks! I'll try it in next 10 mins.
  • RussianVodka
    RussianVodka almost 10 years
    Thanks, man! I used one from there already... I'm JS newbie, so i can't really judge your code. But THANKS! I think that will be useful! :)