Make text fit its parent size using JavaScript
Tags: javascript, html, fonts, tutorial
Published: 2021-03-03 20:27:00

<small>
*cover image: <a href="https://unsplash.com/photos<span>Photo by <a href="https://unsplash.com/@amadorloureiroblanco?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Amador Loureiro</a> on <a href="https://unsplash.com/s/photos/typography?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText">Unsplash</a></span>
</small>
<hr>

Automatically resizing a text to its parent container can be a big struggle and it becomes nearly impossible if you aim to use CSS only.

To reflect the "popularity" of this issue just take a look at  these StackOverflow questions, asking for nearly the same outcome:

- https://stackoverflow.com/questions/4165836/javascript-scale-text-to-fit-in-fixed-div
- https://stackoverflow.com/questions/16056591/font-scaling-based-on-width-of-container
- https://stackoverflow.com/questions/14431411/pure-css-to-make-font-size-responsive-based-on-dynamic-amount-of-characters
- https://stackoverflow.com/questions/17001302/dynamically-resize-font-size-to-fit-container-size



### There are tools to auto-resize text

Fortunately, there are already some resources and tools out there to do the heavy lifting for you:

- https://css-tricks.com/fitting-text-to-a-container/
- https://github.com/davatron5000/FitText.js
- https://github.com/STRML/textFit
- https://github.com/rikschennink/fitty
- https://github.com/jquery-textfill/jquery-textfill.github.io
- https://github.com/simplefocus/FlowType.JS

Well, here is the thing: I tried a few and none really integrated flawlessly into my code. At least not without bigger overhead. I therefore thought of saving the time and hassle of integration and just took on the issue on my own. **It turned out to be easier than I supposed.**



### Let's try on our own

There were four use cases I encountered and I'd like to show a potential implementation with additional explanation for each of them.

If you feel overwhelmed or found that I used shortcuts that I did not explain well enough, then please leave a comment so this can be improved. It's good to have an online editor, like jsFiddle or CodePen open to follow the seteps interactively.

The use cases I want to cover are

1. [Container with fixed height and fixed width](#fixed-height-fixed-width)
2. [Container with fixed width and auto height](#fixed-width-auto-height)
3. [Container with auto width and fixed height](#auto-width-fixed-height)
4. [Container, which can be resized by users](#resize-by-user)

The following sections will use the same simple HTML example for all the use cases, which differ mostly by different CSS.



## <a name="fixed-height-fixed-width"></a>1. Container with fixed height and fixed width

For this use case we simply have to check, whether the text-wrapping element (a `<span>`) overflows on the height and while not, simple increase font-size by 1px.

Consider the following two panels:

```html
<div class="parent">
  <div class="text-container" data-id=1>
    <span class="text">
      This Text is a bit longer
      and should be wrapped correctly
    </span>
  </div>
</div>

<div class="parent">
  <div class="text-container" data-id=2>
    <span class="text">
      This text
    </span>
  </div>
</div>
```

Consider the following CSS for them:

```css
.parent {
  margin: 2%;
  width: 300px;
  height: 50px;
  padding: 15px;
  background: grey;
  color: white;
  display: block;
}

.text-container {
  width: 100%;
  height: 100%;
}

.text {
  font-size: 12px;
  display: block;
}
```

The default sized texts in the panels currently looks like this:

![default sized text in fixed containers](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1nbsi0e2dg5kcj1j8vlx.png)

We can make use of the "overflow" of the text towards it's container (the div with the `text-container` class). Let's change the CSS a bit (for better visualization):

```css
.text-container {
  border: 1px solid;
  width: 100%;
  height: 100%;
}

.text {
  font-size: 32px;
  display: block;
}

body {
  background: #33A;
}
```

The text now clearly overflows it's container:

![fixed size font overflows container](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ovvbqcbpsem2gcskitod.png)



### Calculate the overflow

We can make further use of this, if we can calculate this overflow of the DOM element:

```javascript
const isOverflown = ({ clientHeight, scrollHeight }) => scrollHeight > clientHeight
```

Leveraging this circumstance we can aim for an algorithmic logic for our text resizing function:

We can "try" to increase the font size step-wise by 1 pixel and test again, whether the element is overflowing it's parent or not.

If the element overflows, we know, that the previous step (one pixel less) is not overflowing and thus our best fit.

### A first implementation

The above described logic implies a function, that receives an element and it's parent and iterates from a minimal value (12, for `12px`) to a maximum value (say 128) and sets the `style.fontSize` property to the current iteration index until overflow occurs. Then re-assignes the last iteration's index.

A simple implementation could look like this:

```javascript
const resizeText = ({ element, parent }) => {
  let i = 12 // let's start with 12px
  let overflow = false
  const maxSize = 128 // very huge text size

  while (!overflow && i < maxSize) {
    element.style.fontSize = `${i}px`
    overflow = isOverflown(parent)
    if (!overflow) i++
  }

  // revert to last state where no overflow happened:
  element.style.fontSize = `${i - 1}px`
}
```

Calling this function for the first `text` element and it's parent produces a fair result:

```javascript
resizeText({
  element: document.querySelector('.text'),
  parent: document.querySelector('.text-container')
})
```
![text fitting size](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lagh32foj7wdpazr9dcg.png)



### Add more options

Of course we want to be flexible and thus make the function more configurable:

- allow to only add a querySelector or querySelectorAll and resolve the parent automatically
- allow to pass a custom min and max value
- allow to use different steps than `1` (use float values for even more precise fitting)
- allow to use a differnt unit than `px`

The final code could look like this:

```javascript
const isOverflown = ({ clientHeight, scrollHeight }) => 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)
    	
      if (!overflow) i += step
  	}

  	// revert to last state where no overflow happened
  	el.style.fontSize = `${i - step}${unit}`
  })
}
```

Let's call it for all of our `.text` elements and use a step of `0.5` for increased precision:

```javascript
resizeText({
  elements: document.querySelectorAll('.text'),
  step: 0.5
})
```

It finally applies to both elements:

![all texts fitting fixed containers](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7fsz0o089gdkn78h4shb.png)



## <a name="fixed-width-auto-height"></a>2. Container with fixed width and auto height

Consider the same html but a differnt CSS now:

```css
body {
  background: #A33;
}

.parent {
  margin: 2%;
  width: 150px;
  height: auto;
  min-height: 50px;
  padding: 15px;
  background: grey;
  color: white;
  display: block;
}

.text-container {
  width: 100%;
  height: 100%;
  border: 1px solid;
}

.text {
  font-size: 12px;
  display: block;
}
```

The containers now have a fixed width, a minimal height but can grow dynamically (`height: auto`) if the content overflows. The yet untouched text looks like this:

![not resized with fixed width / auto height](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n0rgu6n4nw5x35e9iylq.png)

Let's see how it looks if we manually increase the font size:

```css
.text {
  font-size: 48px;
  display: block;
}
```
![manually sized font in fixed width / auto height](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t5wknen272piv1ppheiw.png)



### Add horizontal overflow checks

The height "grows" but we get an overflow for the width now.
Fortunately we can use our previous code with just a slight modification. It currently just checks for vertical overflow (using height values) and we just need add checks for horizontal overflow:

```javascript
const isOverflown = ({ clientWidth, clientHeight, scrollWidth, scrollHeight }) => (scrollWidth > clientWidth) || (scrollHeight > clientHeight)
```

This is it. The result will now look great, too:

```javascript
resizeText({
  elements: document.querySelectorAll('.text'),
  step: 0.25
})
```
![resized text with fixed width and auto height](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vk0yq8ffgd4mwckikqw8.png)



## <a name="auto-width-fixed-height"></a> 3. Container with fixed height and auto width

For this case we only need to change our CSS, the functions already do their work for use here.

The default looks like so:

```css
body {
  background: #3A3;
}

.parent {
  margin: 2%;
  width: auto;
  min-width: 50px;
  height: 50px;
  min-height: 50px;
  padding: 15px;
  background: grey;
  color: white;
  display: inline-block;
}

.text-container {
  width: 100%;
  height: 100%;
  border: 1px solid;
}

.text {
  font-size: 12px;
  display: block;
}
```
![not resized fixed height / auto width](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sr495i0gnblpwdublah4.png)

Manually changing the font size results in this:

```css
.text {
  font-size: 48px;
  display: block;
}
```
![manually resized fixed height auto width](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/27x9gml6bp5hiwjy9sf6.png)

Using our function we finally get it right:

```javascript
resizeText({
  elements: document.querySelectorAll('.text'),
  step: 0.25
})
```
![resized text fixed height auto width](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hflj16ko3q8dip0ba0c9.png)

There was no need for additional code here. :tada:


## <a name="resize-by-user"></a> 4. Container that can be resized by users

This is the trickiest part, but thanks to CSS3 and new web standards we can tackle it with just a few lines of extra code. Consider the following CSS:

```css
body {
  background: #333;
}

.parent {
  margin: 2%;
  width: 150px;
  height: 150px;
  padding: 15px;
  background: grey;
  color: white;
  overflow: auto;
  resize: both;
}

.text-container {
  width: 100%;
  height: 100%;
  border: 1px solid;
  display: block;
}

.text {
  font-size: 12px;
  display: block;
}
```

The `resize` property allows us to resize the most upper-level parent containers:

![unresized with resizable containers](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i6wxj5g39t9s8pqfhaxv.png)

The resize functionality is natively [implemented by (most) modern browsers](https://caniuse.com/?search=resize) along with the displayed handle on the bottom right of the containers.

Users can now freely resize the containers and therefore, our logic changes a bit:

- observe a change in the container, caused by the resize event
- if the change happens, call a function, that resizes the text
- optionally use a throttling mechanism to reduce the number of resize executions per second



### Observe changes using `MutationObserver`

For the observation part we make use of the native [Mutation Observer implementation](https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver) that [all modern browsers do support](https://caniuse.com/mutationobserver).

However, we can't observer a change in the `.text` but only in the most outer container, which is in our case `.parent`. Additionally, the `MutationObserver` requires a single node to observe, so we need to iterate over all `.parent` containers to support multiple elements:

```javascript
const allParents = document.querySelectorAll('.parent')
allParents.forEach(parent => {
  // create a new observer for each parent container
  const observer = new MutationObserver(function (mutationList, observer) {
      mutationList.forEach( (mutation) => {
        // get the text element, see the html markup
        // at the top for reference
        const parent = mutation.target
        const textContainer = parent.firstElementChild
        const text = textContainer.firstElementChild
        
        // resize the text
        resizeText({ element: text, step: 0.5 })
    });
  })

  // let's observe only our required attributes
  observer.observe(parent, {
    attributeFilter: ['style']
  })
})
```

This plays out very nice most at the time:
![resized by user](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/macab4zo8k7oev6wmje3.png)

Beware! There are still glitches when resizing:
![resized by user with glitches](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6n81xdt24o3se10h6ber.png)

We can actually fix 99.9% of them by applying different `overflow` CSS properties:

```css
.parent {
  margin: 2%;
  width: 150px;
  height: 150px;
  padding: 15px;
  background: grey;
  color: white;
  overflow-x: auto;
  overflow-y: hidden;
  resize: both;
}
```

If anyone knows a better way to get 100% rid of the glitches, please comment :-)



### Optional: add throttling

Finalizing the whole functionality we may add a [throttle functionality](https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_throttle) to reduce the number of calls to the `resizeText` method:

```javascript
const throttle = (func, timeFrame) => {
  let lastTime = 0
  return (...args) => {
      const now = new Date()
      if (now - lastTime >= timeFrame) {
          func(...args)
          lastTime = now
      }
  }
}

const throttledResize = throttle(resizeText, 25)
```
Use it in the observer instead of `resizetText`:

```javascript
// ...
const parent = mutation.target
const textContainer = parent.firstElementChild
const text = textContainer.firstElementChild
        
throttledResize({ element: text, step: 0.5 })
// ...
```



## Summary

I reflected my first experiences in resizing text dynamically and hope that it helps people to get into the topic and understand the mechanisms in order to evaluate existing libraries.

This is by far not a generic enough approach to become a one-for-all solution. However, there article shows, that it's achievable without the need for third-party code as modern browsers bring already enough functionality to build your own resize tool in ~50 lines of code.

Any suggestions for improvements are very welcomed and I hope you, the reader gained something out of this article.




## Resources used by the author for this article

- https://developer.mozilla.org/en-US/docs/Web/API/Node/childNodes
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from
- https://developer.mozilla.org/en-US/docs/Web/CSS/resize
- https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
- https://caniuse.com

- https://github.com/you-dont-need/You-Dont-Need-Lodash-Underscore#_throttle
- https://stackoverflow.com/a/9541579/3098783
- https://stackoverflow.com/questions/5712596/how-can-i-let-a-div-automatically-set-it-own-width

- https://jsfiddle.net/
- https://codepen.io/

<hr>
I regularly publish articles here on dev.to about **Meteor** and **JavaScript**. If you like what you are reading and want to support me, you can [send me a tip via PayPal](https://paypal.me/kuesterjan).

You can also find (and contact) me on [GitHub](https://github.com/jankapunkt/), [Twitter](https://twitter.com/kuester_jan) and [LinkedIn](https://www.linkedin.com/in/jan-kuester/).