- Saved searches
- Use saved searches to filter your results more quickly
- License
- nbrunt/TextFit
- Name already in use
- Sign In Required
- Launching GitHub Desktop
- Launching GitHub Desktop
- Launching Xcode
- Launching Visual Studio Code
- Latest commit
- Git stats
- Files
- README.md
- Make text fit its parent size using JavaScript
- Let’s try on our own
- 1. Container with fixed height and fixed width
- Calculate the overflow
- A first implementation
- Add more options
- 2. Container with fixed width and auto height
- Add horizontal overflow checks
- 3. Container with fixed height and auto width
- 4. Container that can be resized by users
- Observe changes using MutationObserver
- Optional: add throttling
- Summary
- Resources used by the author for this article
Saved searches
Use saved searches to filter your results more quickly
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session. You switched accounts on another tab or window. Reload to refresh your session.
A smart JavaScript module to measure text in pixels, choose the best fit font size or trim a sentence to fit a container
License
nbrunt/TextFit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Sign In Required
Please sign in to use Codespaces.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching GitHub Desktop
If nothing happens, download GitHub Desktop and try again.
Launching Xcode
If nothing happens, download Xcode and try again.
Launching Visual Studio Code
Your codespace will open once ready.
There was a problem preparing your codespace, please try again.
Latest commit
Git stats
Files
Failed to load latest commit information.
README.md
A smart JavaScript module to measure text in pixels, choose the «best fit» font size or trim a sentence to fit a container.
Returns the length of string in pixels, given the font face and size of the target element.
console.log($('#test1').textfit('width','Testing'));
Adjusts the font size of the target element so that the string fits it perfectly. The target element must have an absolute width and height.
div id pl-s">test2"> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. div>
#test2 < border: 1px solid red; width: 120px; height: 50px; >
Trims the contents of the target element to the size of the element. The target element must have an absolute width and height.
div id pl-s">test3"> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. div>
#test3 < border: 1px solid blue; width: 300px; height: 40px; >
Make text fit its parent size using JavaScript
Fortunately, there are already some resources and tools out there to do the heavy lifting for you:
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
The following sections will use the same simple HTML example for all the use cases, which differ mostly by different CSS.
1. Container with fixed height and fixed width
For this use case we simply have to check, whether the text-wrapping element (a ) overflows on the height and while not, simple increase font-size by 1px.
Consider the following two panels:
class="parent"> class="text-container" data-id=1> class="text"> This Text is a bit longer and should be wrapped correctly class="parent"> class="text-container" data-id=2> class="text"> This text
Consider the following CSS for them:
.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:
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):
.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:
Calculate the overflow
We can make further use of this, if we can calculate this overflow of the DOM element:
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:
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:
resizeText( element: document.querySelector('.text'), parent: document.querySelector('.text-container') >)
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:
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:
resizeText( elements: document.querySelectorAll('.text'), step: 0.5 >)
It finally applies to both elements:
2. Container with fixed width and auto height
Consider the same html but a differnt CSS now:
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:
Let’s see how it looks if we manually increase the font size:
.text font-size: 48px; display: block; >
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:
const isOverflown = ( clientWidth, clientHeight, scrollWidth, scrollHeight >) => (scrollWidth > clientWidth) || (scrollHeight > clientHeight)
This is it. The result will now look great, too:
resizeText( elements: document.querySelectorAll('.text'), step: 0.25 >)
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:
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; >
Manually changing the font size results in this:
.text font-size: 48px; display: block; >
Using our function we finally get it right:
resizeText( elements: document.querySelectorAll('.text'), step: 0.25 >)
There was no need for additional code here. 🎉
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:
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:
The resize functionality is natively implemented by (most) modern browsers 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 that all modern browsers do support.
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:
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:
Beware! There are still glitches when resizing:
We can actually fix 99.9% of them by applying different overflow CSS properties:
.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 to reduce the number of calls to the resizeText method:
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 :
// . 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
You can also find (and contact) me on GitHub, Twitter and LinkedIn.