53

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.

I'd probably want to start with a maximum font size and while the text is too big to fit the container, shrink the font size until it fits and the font must be displayed as a single line.

gre_gor
  • 6,669
  • 9
  • 47
  • 52
user2613399
  • 715
  • 1
  • 6
  • 14

11 Answers11

35

Say you have this:

<div id="outer" style="width:200px; height:20px; border:1px solid red;">
    <div>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer mollis dui felis, vel vehicula tortor cursus nec</div>
</div>

Then you can do something like:

$(document).ready(function () {
    resize_to_fit();
});

function resize_to_fit(){
    var fontsize = $('div#outer div').css('font-size');
    $('div#outer div').css('fontSize', parseFloat(fontsize) - 1);

    if($('div#outer div').height() >= $('div#outer').height()){
        resize_to_fit();
    }
}

Working Sample

$(document).ready(function() {
  resize_to_fit();
});

function resize_to_fit() {
  var fontsize = $('div#outer div').css('font-size');
  $('div#outer div').css('fontSize', parseFloat(fontsize) - 1);

  if ($('div#outer div').height() >= $('div#outer').height()) {
    resize_to_fit();
  }
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div id="outer" style="width:200px; height:20px; border:1px solid red;">
  <div>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer mollis dui felis, vel vehicula tortor cursus nec</div>
</div>
Jithin Raj P R
  • 6,667
  • 8
  • 38
  • 69
putvande
  • 15,068
  • 3
  • 34
  • 50
32

Based on the logic of the top answer here, I created a no-jQuery version,

const input = document.querySelector('input');
const output = document.querySelector('.output');
const outputContainer = document.querySelector('.container');

function resize_to_fit() {
  let fontSize = window.getComputedStyle(output).fontSize;
  output.style.fontSize = (parseFloat(fontSize) - 1) + 'px';
  
  if(output.clientHeight >= outputContainer.clientHeight){
    resize_to_fit();
  }
}

function processInput() { 
  output.innerHTML =this.value;
  output.style.fontSize = '100px'; // Default font size
  resize_to_fit();
}

input.addEventListener('input', processInput);
<input type="text" placeholder="Add text input here" style="margin: 10px; width:50%;">

<div id="outer" class="container" 
style="width:80%; height:100px; border:2px solid red; font-size:20px;">
  
<div class="output" style="word-break: break-all; word-wrap: break-word;">
</div>

</div>
Tharindu Sathischandra
  • 1,654
  • 1
  • 15
  • 37
5

There is no need to iterate over font sizes. You just need to calculate the current size of the text and the size of the container and apply the scaling.

const text_el = document.querySelector("#text");
const container_el = document.querySelector("#container");

text_el.addEventListener("input", function(e) {
  container_el.children[0].textContent = this.value;
  resize2fit(container_el)
});

function resize2fit(el) {
  if (!el.parentElement) return;
  el.style.setProperty("--font-size", "1em");
  const {width: max_width, height: max_height} = el.getBoundingClientRect();
  const {width, height} = el.children[0].getBoundingClientRect();
  el.style.setProperty("--font-size", Math.min(max_width/width, max_height/height)+"em");
}

window.addEventListener("resize", function(e) {
    resize2fit(container_el);
});
resize2fit(container_el);
#container {
  width: 100%;
  height: 100px;
  font-size: var(--font-size, 1em);
  white-space: nowrap;
  border: 1px solid blue;
}
#container > * {
  display: inline-block;
}
<input id="text" value="lorem ipsum" />
<div id="container">
  <span>lorem ipsum</span>
</div>
gre_gor
  • 6,669
  • 9
  • 47
  • 52
  • Why don't you have more votes up ? Meausring a ratio is a thousand time more effective than iterating over each possible font-size value and waiting for re-rendering in between... Anyway, I'd just add to that solution that everything sized inside the childContainer have to be en `em` unit to keep everything proportional :) – Florent Descroix Apr 25 '23 at 09:29
3

If by text length you mean character count, there is this article which points to a short jquery snippet Change Font Size dynamically based on Character count

http://jsfiddle.net/jfbrLjt2/

 $('.text').each(function(){
 var el= $(this);
   var textLength = el.html().length;
    if (textLength > 20) {
        el.css('font-size', '0.8em');
    }
});
Kalamarico
  • 5,466
  • 22
  • 53
  • 70
Akin Hwan
  • 607
  • 1
  • 9
  • 26
2

Getting the size in pixels of a string of text is a hard thing to do and depends a lot on the browser and the user's settings, so it will probably be hard to come up with a solution that works in all cases.

By "display user entered text" do you mean the user is typing into a textbox? If so, you can attach a keypress listener that checks the length of the value of the text, then adjusts the font-size accordingly. Here's an example, though you will probably need to change the lengths and font-sizes to meet your needs:

Working Demo

$('#text').on('keypress', function(e) {
    var that = $(this),
        textLength = that.val().length
    ;

    if(textLength > 30) {
        that.css('font-size', '5px');
    } else if(textLength > 20) {
        that.css('font-size', '10px');
    } else if(textLength > 10) {
        that.css('font-size', '15px');
    }
});
cfs
  • 10,610
  • 3
  • 30
  • 43
  • what if the text is obtained from an external source( user will not type in box) and that text must be displayed in a div? how will we know the size of text? – user2613399 Aug 14 '13 at 17:11
  • See @putvande's answer, I think that might be what closer to what you're looking for – cfs Aug 14 '13 at 17:17
1
<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <style>
        #div1{
            width: 200px;
            height: 200px;
            font-size: 32px;
            line-height: 1.2em;
        }
    </style>
    <script type="text/javascript" src="js/jquery-1.9.1.min.js"></script>
    <script>
        $(function(){
            n = 1;
            while(n==1){
                n = 0
                if ($('#div1 .holder').outerHeight()>$('#div1').outerHeight()){
                    var fz = parseInt($('#div1').css('font-size'));
                    $('#div1').css({'font-size' : fz-1});
                    n = 1
                } else {n = 0}
            }
        });
    </script>
</head>
<body>
    <div id="div1">
        <div class="holder">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.
I'd probably want to start with a maximum font size and while the text is too big to fit the container, shrink the font size until it fits and the font must be displayed as a single line. Is it possible to do this using only css and html.</div>
    </div>
</body>
</html>
Anon
  • 2,854
  • 14
  • 18
1

Thanks putvande for letting me know the general method. Here is an improved version.

  1. Get rid of the callback hell.
  2. Fixed some bugs may cause page hang.

Same HTML:

<div id="outer" style="width:200px; height:20px; border:1px solid red;">
    <div>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer mollis dui felis, vel vehicula tortor cursus nec</div>
</div>

Here is the function:

resize_to_fit($('#outer'), $('#outer div'));

function resize_to_fit(outer, inner) {
    while(inner.height() > outer.height()) {
        var fontsize = parseInt(inner.css('font-size')) - 1;
        inner.css('font-size', fontsize);
        // some browsers(chrome) the min font return value is 12px
        if(fontsize <= 1 || parseInt(inner.css('font-size')) >= fontsize+1)
            break;
    }
}
Joe
  • 37
  • 4
  • whats wrong with recursion and how is that callback hell? Callback hell, as I've understood it, is usually a near endless `func().next().then().etc()..` or really heavy nested anonymous function passing... right? – Canis Oct 28 '16 at 14:17
  • he probably meant call-stack, not callback. It's not good practice to recurse, as it can indefinitely fill the call-stack, possibly causing stack overflow. – uTILLIty Jan 19 '18 at 16:08
  • @Joe why don't you use the elements parentNode but explicily expect it to be passed? – uTILLIty Jan 19 '18 at 16:10
1

Using very little Javascript, you can assign a CSS class with the length of the string, eg: text-length-5 or text-length-20

Then use exclusively CSS to target different font-size according to those class names.

It probably won't work for every case, but it did very well for mine, where the max length was about 10-12 chars.

If you have 100+ or 1000+ chars you can also make a function that receives the length and returns a CSS class, then style base on those ranges.


HTML:

<div class="box">ABC</div>
<div class="box">ABCDE</div>

Javascript (jQuery, but the same idea can be applied to React, or vanilla JS)

$(".box").each((i, el)=> {
  const length = $(el).text().length;
  $(el).addClass("text-length-"+length);
})

CSS:

.box {
  font-size: 16px;
}
.box.text-length-4,
.box.text-length-5,
.box.text-length-6 {
  font-size: 12px;
}
ecairol
  • 6,233
  • 1
  • 27
  • 26
1

Although this is an old question and already has several answers (mostly based on changing the font size until the content fits), I recently had to do something similar with one of our projects.

Initially I used an edited version of Joe's answer (https://stackoverflow.com/a/33492161/587526), but then I began to think about it and realised that the same effect can be obtained much more easily by using a simple "transform: scale(x)" operation. An added advantage is that the content is scaled to exactly fit in the container, whereas adjusting font-size may result in suboptimal text sizes.

Calculating the value of the scale factor is ridiculously simple, just divide the parent container width by the child container width. If the child content is larger than the parent, the resulting value will be < 1.0 - and passing this to "transform: scale()" results in a perfectly redimensioned child element.

Here is the function I use. It uses jQuery, but could also be easily updated to use the available DOM functions directly.

The code is mostly self-explanatory, but I will point out that the child's "display" is initially changed to "inline-block" because elements such as <p>,<span> etc. do not otherwise report their widths.

Something else to be aware of, using "transform: scale()" may affect margins. In my case I needed to ensure that the child elements were centered inside the parent container so I made use of display: flex:

.parent-container {
    display: flex;
    flex-direction: column;
    align-items: center;
}

Hope someone finds this useful!

function resizeToFit(parent, child) {
    let $parent = jQuery(parent);
    let $child = jQuery(child);

    // Temporarily redefine child's display so we
    // can obtain the real width
    let $display = $child.css('display');
    $child.css('display', 'inline-block');


    // Remove any existing inline transform
    $child.css('transform', '');

    // Calculate the scale factor
    let factor = $parent.width() / $child.width();
    if (factor < 1.0) {
        let scale = 'scale(' + factor + ')';
        $child.css({
          '-webkit-transform' : scale,
          '-moz-transform'    : scale,
          '-ms-transform'     : scale,
          '-o-transform'      : scale,
          'transform'         : scale
        });
    }

    // Restore the original display value
    $child.css('display', $display);
};
John C
  • 131
  • 1
  • 6
0

Whoever wants a dynamic function that depends on the width of the element (based on putvande answer):

        $(document).ready(function () {
            var arrEl = ['div1', 'div2', 'div3'];
            resize_to_fit(arrEl);
        });

        function resize_to_fit(arr){
            for (var el in arr) {
                var jEl = $('.' + arr[el] + ' span');
                var fontsize = jEl.css('font-size');
                jEl.css('fontSize', parseFloat(fontsize) - 1);
                if (jEl.width() >= $('.' + arr[el]).width()){
                    resize_to_fit([arr[el]]);
                }        
            }
        }

and the HTML:

<div class="div1">
    <span>Lorem Ipsum</span>
</div>
<br/>
<div class="div2">
    <span>Lorem Ipsum 2</span>
</div>
<br />
<div class="div3">
    <span>Lorem Ipsum 3</span>
</div>

whith CSS:

.div1, .div2, .div3 {
    width: 207px;
    white-space: nowrap;
}

Notice you only set font-size to span inside, and not to the outer div.

SRachamim
  • 931
  • 2
  • 8
  • 20
0

You can try this one, it's working 100% for me.

  function resize(){
    const isOverflown = ({ clientWidth, scrollWidth, clientHeight, scrollHeight}) => scrollWidth > clientWidth || scrollHeight > clientHeight

    const resizeText = ({ element, elements, minSize = 10, maxSize = 512, step = 1, unit = 'px' }) => {
      (elements || [element]).forEach(el => {
        let i = minSize
        let overflow = false

            const parent = el.parentNode

        while (!overflow && i < maxSize) {
            el.style.fontSize = `${i}${unit}`
            overflow = isOverflown(parent)
            el.style.lineHeight = ((el.style.fontSize).slice(0, -2))*1.2+"px"
          if (!overflow) i += step
        }

        el.style.fontSize = (`${i - step - 1}${unit}`)
      })
    }

    resizeText({
      elements: document.querySelectorAll('.text'),
      step: 0.5
    })
  }
  
    if (document.readyState === 'complete') {
    resize();
  } else {
    window.onload = resize();
  }
<div id="outer" style="width:200px; height:80px; border:1px solid red;">
    <div class="text">Lorem ipsum</div>
</div>

<div id="outer" style="width:200px; height:80px; border:1px solid red;">
    <div class="text">Lorem ipsum dolor sit amet, consectetur adipiscing 
    elit. Integer mollis dui felis, vel vehicula tortor cursus nec.</div>
</div>