Coder Social home page Coder Social logo

textfit's Introduction

textFit - Hassle-Free Text Fitting

A fast, dependency-free text sizing component that quickly fits single and multi-line text to the width and/or height of its container.

Example

Capabilities

textFit is:

  • Fast, using binary search to quickly fit text to its container in log n time, making it far faster than most solutions.
  • Dependency-free.
  • Small. 4.1KB minified and 1.5KB gzipped.
  • Supports both horizontal and vertical centering, including vertical centering with Flexbox for maximum accuracy.
  • Supports any font face, padding, and multiline text.

Browser Support

textFit supports IE9+, Firefox, Chrome, Opera, and most mobile browsers. If you find an incompatibility, please file an issue.

If you require IE <= 8 support, please use the jQuery version. Functionality is identical between v1.0 and v2.0, the only change was the removal of the jQuery dependency.

Changelog

v2.3.1

  • Fix #20 - properly iterate over HTMLCollection objects.

v2.3.0

  • Added alignVertWithFlexbox. This does better vertical alignment and fixes #14.

v2.2.0

  • Throw errors instead of just printing to console when missing height/width.
    • Removed options.suppressErrors. Wrap in try/catch instead if you really need this.
  • Slight refactor.
  • Added automatic build on prepublish.

v2.1.1

  • Fixed a bug with alignVert when reprocessing.
  • Added full UMD shim and published to npm.

v2.1

  • Reworked alignVert.
  • reProcess is now true by default. Set to false if you want to fire-and-forget on potentially processed nodes. This was originally false by default because it was being used in an infinite scrolling list.

v2.0

  • Removed jQuery dependency.

Usage

<div class="box" style="width:300px;height:300px">
  Fit me, I am some text
</div>
// textFit accepts arrays
textFit(document.getElementsByClassName('box'));
// or single DOM elements
textFit(document.getElementsByClassName('box')[0]);
// Use jQuery selectors if you like.
textFit($('#box')[0])

The text will scale until it reaches the horizontal or vertical bounds of the box. Explicit width and height styles are required in order to fit the text.

Advanced Usage

Multiline Strings

If your text has multiple lines, textFit() will automatically detect that and disable white-space: no-wrap! No changes are necessary.

<div class="box" style="width:300px;height:300px">
  This text <br>
  Has multiple lines <br>
  Fit me!
</div>
textFit(document.getElementsByClassName('box'))

If, for some reason, the automatic detection is not working out for you, use the following to explicitly turn on multiLine fitting:

textFit(document.getElementsByClassName('box'), {multiLine: true})

Horizontal/Vertical Centering

<div class="box" style="width:300px;height:300px">
  This text <br>
  Has multiple lines <br>
  And wants to be centered horizontally and vertically<br>
  Fit me!
</div>
textFit(document.getElementsByClassName('box'), {alignHoriz: true, alignVert: true})

Minimum and Maximum Sizes

Sometimes you want to make sure that your text remains sanely sizes if it is very short or very long. textFit has you covered:

<div class="box" style="width:300px;height:300px">
  Short Text
</div>
textFit(document.getElementsByClassName('box'), {minFontSize:10, maxFontSize: 50})

Implementation Details

textFit determines reasonable minimum and maximum bounds for your text. The defaults are listed below.

To ensure accurate results with various font-faces, line-heights, and letter-spacings, textFit resizes the text until it fits the box as accurately as possible. Unlike many similar plugins, textFit uses binary search to find the correct fit, which speeds the process significantly. textFit is fast enough to use in production websites.

textFit() is a synchronous function. Because of this, resizes should be invisible as the render thread does not have a chance to do a layout until completion. Normal processing times should be < 1ms and should not significantly block renders.

Default Settings

From the source, for reference:

settings = {
    alignVert: false, // if true, textFit will align vertically using css tables
    alignHoriz: false, // if true, textFit will set text-align: center
    multiLine: false, // if true, textFit will not set white-space: no-wrap
    detectMultiLine: true, // disable to turn off automatic multi-line sensing
    minFontSize: 6,
    maxFontSize: 80,
    reProcess: true, // if true, textFit will re-process already-fit nodes. Set to 'false' for better performance
    widthOnly: false, // if true, textFit will fit text to element width, regardless of text height
    alignVertWithFlexbox: false, // if true, textFit will use flexbox for vertical alignment
};

License

MIT

textfit's People

Contributors

ahobsonsayers avatar alpha-and-omega avatar mat-thieu avatar remonator avatar strml avatar tjreigh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

textfit's Issues

Doesn't work with requirejs

Hello, I really like your plugin and it has saved half of my day.

Please remove false from this line because it prevents module to be included with requirejs:

if (false && typeof define === "function" && define.amd) {

TextFit not working with ul/ol

Hi, First of all, such an amazing plugin this is!
Though it works on most of use cases, it does not seem to be resizing when applied on an ordered/ unordered list.

Any workaround for the same?

factor out jQuery

It'd be great if this didn't depend on jQuery, and thus could be used with/without any framework. Would be a great component to have.

Support height-only/dynamic width?

Quite often, I find that setting an explicit width is inconvenient, because of unknown screen widths, but I hate a pretty good idea of what the height should be. Would it be possible to have a mode where we can expand vertically, and just leave the width to the browser?

Thanks!

Text shrinks more than necessary (AKA no odd font-sizes?)

I am trying to fit a single line of text ({ minFontSize: 16, maxFontSize: 30 }). But the text is often smaller than it needs to be, and when I increase the font-size property by 1px it still fits in the container. The library seems to skip odd font-sizes for some reason.

I suspect the binary search algorithm may be the problem.

Examples not working

Firefox 67.0.4 (64-bit)
Shouldn't the text expand to fit the container? This is what I see:
textfit_ex

Doesn't work in Firefox

Just tested your demos in Firefox on OSX and nothing happens at all. Works just fine in Safari and Chrome but Firefox will not do anything.

skarmavbild 2013-08-19 kl 13 00 46

don't work with h1

i'm really happy to have found this. it's exactly what i need.

but my text i want to make huge is marked as h1 and that overwrites the font-size.

my idea was to add !important to the font-site added by textFit but don't know where.

http://jsfiddle.net/HEcWQ/

if you delete the h1 it works as it should.

can you fix it?

Feedback / suggestions

Great plugin. I must have gone through several dozen plugins claiming to adjust font size before I found this one.

Line Heights & Corner case bugs

FYI at really small font sizes it breaks. If you set a div to 5px tall by 500px wide, the line-height of the div is never modified, this pushes the span to a new line, like this JSFiddle I made to show the issue: https://jsfiddle.net/zhtt7t8p/5/

You can see there the span doesn't line up. I fixed it in my code by forking your code & setting the line-heights. For the "parent", I do this in the binary search [probably crap code but shows the fix]:

$(innerSpan).parent().css('line-height', '0px'); // set parent's line height to 0
$(innerSpan).css('line-height', mid + 'px'); // set child line height to same as font size

This was required to ensure that the inner span doesn't overflow it's parent. I also modified the part after the binary search:

// Sub 1 at the very end, this is closer to what we wanted.
$(innerSpan).css('line-height', (mid-1) + 'px');
innerSpan.style.fontSize = (mid - 1) + 'px';

Callback & "responsive" resizing

Since your plugin runs the sizing logic once when invoked, and doesn't bind to any resize events, I wrote my own plugin that runs yours when invoked, and again when the elements are resized. However your algorithm causes more resizes to fire, which causes an infinite loop. I worked around this by setting overflow:hidden to prevent unwanted resize events. Since your algorithm is asynchronous (it takes time to try the different font sizes until it is done), the resize events can fire while the algorithm is still running, creating race conditions where different instances of the algorithm are fighting over the same piece of text simultaneously. To workaround this my plugin simply wraps your plugin in Underscore's _.throttle() method to ensure it won't be called more frequently than once a second.

If your plugin simply had a callback for when it was done sizing the text, I as a user could unbind the resize event while your algorithm is "working" to only rebind it when your algorithm is "done". This would fix a lot of race conditions & make it easier to bind the plugin to resize events of it's parent element.

Browser Bugs

I'm still working on this one but sometimes if the parent has some width (let's say 500px), and you put the span of text there inside it & start iterating through font sizes, sometimes I've seen Chrome set the span's width to 501px (1px overflowing its parent). I think it depends on some font settings or letter spacing. Still debugging.

The problem this causes is that your algorithm responds to this by continuing to decrease the font size. For some reason, independent of font size, Chrome is always rendering the inner span at 501px until the font-size is decreased enough for all text to fit on one line.

The only workarounds I can think of are

  1. In the binary search when it's checking if the width/height are acceptable, compare it to the parent's size +3px to give it some "wiggle room" (this can make the fonts too big in 99% of cases).

  2. I noticed another thing we can use to detect this "situation" (browser bug). If the size of the inner span (501px in our case) has not changed between multiple iterations of the binary search (and that size is within some acceptable threshold of the target size), we can conclude that the font size is acceptable, and assume that the 1px overflow is due to a browser bug that will not be affected by any further font-size decreases (at least not without making the text so undesirably small that it defeats the purpose of the plugin)

How to force or detect new line by textarea

Hi, I'm trying to solve this problem in my app that using textFit but my every try ended with fail :(

How to force textFit to make new line? For example textFit get text from textarea, user using enter makes new line but textFit don't create new line. Any tips for solve this problem?

Callback after completion?

Thanks for an awesome library, works great!

I want to use the resulting font size after textFit() has been executed, something like this:

fitText(...);
doSomethingElse();

I'm finding that doSomethingElse() is being called before fitText() has done it's thing, so the I'm getting the original font size. Wrapping doSomethingElse() in a timeout helps, but introduces a lag into the UI.

Is there a way to set-up a callback so that doSomethingElse() is only run after fitText() has finished?

Support 'em' unit for min and max font-size

Hello,

In my use case, the size of the divs vary depending on the resolution of the user's screen and how they decide to display the page, so I can't use fixed sizes for min and max font-size.

Do you have any plans to allow for 'em' units for these options?

Cheers!

Seb

accept string as well

right now it accepts element i.e. $('.box') or document.getElementsByClassName('box')

please allow us to pass the string directly with box class

thank you

Doesn't work on "hidden" elements

I have a couple divs that I show/hide using element.hidden = true etc. My code sets all the text, then unhides. If I run the textFit(myElement) before setting hidden to false, then it defaults to the smallest text size.
Is there a way to make this work on hidden elements?

Max lines option

Is it possible to set max lines option to not exceed X lines when scaling text down?

Taking scrollbars into account

Sometimes scrollbars which are initially visible disappear when adjusting text size. This breaks the logic and forces the smallest possible text size.

The issue is here:

    // Binary search for best fit
    while (low <= high) {
      mid = parseInt((low + high) / 2, 10);
      innerSpan.style.fontSize = mid + 'px';
      if(innerSpan.scrollWidth <= originalWidth && (settings.widthOnly || innerSpan.scrollHeight <= originalHeight)){
        low = mid + 1;
      } else {
        high = mid - 1;
      }
    }

When text becomes smaller, scrollbars which are initially visible may disappear. Then innerSpan.scrollWidth becomes larger than originalWidth, thus forcing to make the text size even smaller.

Invalid scaling caused by inconsistency between innerspan.scrollWidth and el.clientWidth

Hi there, I've got an interesting one for you here, with a PR on the way!

To recreate for this specific jsfiddle ( https://jsfiddle.net/y6xej9kv/2/ ) , you need to be on Windows + Chrome, set your OS-level display scaling to 175% (display settings), then in your browser, zoom to 67%. I'm sorry it's a bit obscure but this is how it's recreated for that specific sample.
Any OS, on Chrome zoom to 75%
When all this is done you will see this:
Bug sample
but we're actually expecting this:
Bug sample
and this is likely what you will see if you open the "bugged" jsfiddle without any of the scaling/zoom settings anyway.

So what's going on here is that as soon as you mess around with scaling, some dimensions will fall on floating points, but textfit is using scrollWidth and clientWidth to determine dimensions, which are not only always pure integers, but also rounded differently internally by the browser, which is the root cause of this bug.

If you're running the "bugged" JSfiddle with the settings I mentioned, these will be the values

invalid values
Meaning that the while loop that determines the fitted size can never fulfill correctly.

There are a couple ways to solve this, but in the PR ( fiddle example here https://jsfiddle.net/wh6q7yxb/1/ )I'll opt for replacing both clientWidth and scrollWidth (also clientHeight and scrollHeight to prevent a similar issue) with .getBoundingClientRect() which will always return the exact rendered values as a float. I understand .getBoundingClientRect is not the same as .scrollHeight/width, I don't think using that property there is actually not of much use, because it's a span, by default these can't scroll in any direction so only if someone explicitly made a display: block; out of it can this be a issue.

Does not work in IE8

This plugin is great work and super helpful on the products my company uses. Unfortunately, as of v2-noJQuery the plugin no longer works in IE8. I wasn't able to get the linked example/test page to work in IE8 either.

Hopefully you can get this figured out!

Bind to resize event

Trying to hook this up with to a resize event but it doesn't seem to adjust the sizing. I tried setting reProcess to true but that wouldn't do much either. Do you have a demo or something for handling text resizing on browser resize events?

Thanks for a great piece of code, and that you're so keen on helping out :)

Fit all the <p> inside a div

Is it possible to fit all the p elements inside a div?
I have a div with fixed width and height 300px and 600px, with many p elements inside, I tried with textFit(document.querySelectorAll(".box" + "p")); but nothing changes.
If I remove p tags and just do textFit(document.querySelectorAll(".box")); it works, but I really need paragraphs.
Any way to do this?

Example page doesn't work (Wrong link?)

The example page mentioned in README (links to this page) does not show resized text, all text samples appear very tiny.

Tested with Firefox 76.0 and Edge (Chromium) Version 81.0.416.72 (Official build) (64-bit)

Note: It works correctly on this page, although this page's certificate is expired.

Only works every other time on element that I'm changing text in

I'm getting the set a static height and width on target ellement when I change the text and re run textfit:

Set a static height and width on the target element

I have my initial div set up with text in it and it works just fine. But if I use jquery to change the text value of the div with jquery's text() function, it tells me to set the width and height first, then th next time the text is changed ti works. Eveyr other time I change the text it goes from working, to giving me that error.

Line-height and timing issues.

I found that line-height can break the script if the line-height is larger than the element size. To work around this (I need to adjust the line height to get the font to center), I set the line-height style to normal then call the script and clear the normal setting after completion.

I am getting inconsistent results on the first execution of a fresh browser window. The same element on first calculation of a fresh browser instance will result in 32px but in later calculations, the correct result of 35px is achieved. I thought this would be because the function does not return fast enough so I tried wrapping it into a promise. Does the script have callbacks perhaps onComplete?

Safari is not considering height of container

I am running into an issue where Safari isn't considering the height of the box, but only the width.
For very short labels this will render the font much larger. In Firefox or Chrome, the result is correct.

In the CSS, I specify height of box, as well as line-height.

Any idea, what to adapt to make this work, or is this a known issue?

image

Feature Request: Smaller 'steps'

Thanks for this! Great plugin. Would be nice to be able to specify the size of the font-size 'steps', so that, for instance, half-pixel values could result. I've been trying to modify the plugin to do this without much success.

the module needs a less generic name, it is impossible to find

I've spent about two days trying to figure out how to resize user-inputted and -formatted text of extremely varying length and markup to fit a container with a rigidly set height. Absolutely none of the other plugins I have tried (fitText, flowtype, fillText, any other combination of the words fit, text, and resize with a .js at the end) can do this, because they mostly focus on container width and/or only deal with one level of nesting, usually they just want a container div with a single span inside. My own attempts were not very successful either.

textFit was the very last thing that I found before giving up, and I can't remember what bizarre combination of search terms led me here. I may have even overlooked it previously thinking that it was one I had already tried. It worked instantly without me needing to do anything at all and I am still reeling from the shock. Have you considered maybe giving this INCREDIBLE plugin a less generic name so that it doesn't get swallowed up among all the others that pop up when you type the words fit and text into google? The world needs to know about this! You've solved the unsolveable!

Anyway. Really I just wanted to say thank you so much for making this fantastic and beautiful thing, but I couldn't open an issue just for that. But thank you. I am extremely grateful.

reProcess issue

I had to change this line
innerSpan = $(originalText).find('span.textfitted');
to
innerSpan = $(this).find('span.textfitted');
to make it work.
Otherwise the innerSpan.width() would be null.

Does not work with class as selector

Hello again,

Great that you were able to solve the other trouble I had with the script not working in Firefox. I've stumbled upon what seems to be another bug.

I got it working when using an unique id, but if I use:
textFit(document.getElementsByClassName('content'));
or even textFit($('.content'));

TypeError: el.getAttribute is not a function
if (el.length === 0 || (!settings.reProcess && el.getAttribute('boxfitted'))) {

This is present both in Firefox and in Webkit.

Larger texts

On larger texts (many characters) it keeps the lowest possible font size;

does not work at certain window sizes

Hi, thank you first of all for writing this awesome tool!

For most window sizes textFit works perfect for me. But when I increase my browser window step by step, at some window sizes textFit suddenly does not work anymore, but takes the minimum text size although there is plenty of space in the element. Also the behaviour of the other buttons becomes unpredictable.

The behaviour is also dependent on the text content in the right button and, strangely enough, also on the text content in the other buttons.
The problem is the same in the jquery version.

C.f. the jsfiddle here: http://jsfiddle.net/dqLuqh7y/1/
and a screenshot here: https://www.dropbox.com/s/ia0q49pgxi0jbcf/screenshot.png?dl=0
unfortunately, you have to play with the window size yourself, because jsfiddle does not save the window size.

I want to use your tool for a responsive website / mobile app, but it is crucial that it works steadily. Can you help me?

Text from input box only appearing on single line

This is how I'm trying to implement things:

	jQuery("#form-field-text").on("change keyup paste", function(){
    	jQuery('.canvas-text').text(jQuery(this).val());
    	textFit(jQuery(".canvas-text"), {widthOnly: false});
	});

So a user fills text in the input box at #form-field-text, it automatically gets inserted into .canvas-text at every keypress, and after that textFit runs. It's doing its job, but the text is only appearing on a single line. Check out the codepen here:

https://codepen.io/overbord/pen/rNGNNjx

Table updates do not work

Hello,
Thanks for this nice tool! :-)
One issue still I have:
If I do a textFit for an array : textFit(document.getElementsByClassName('box'));
It does not work.
But if I do a textFit to an element only it works:
textFit(document.getElementsByClassName('box')[0]);
Would you have any idea why?
I am using Chrome. Here is my code:

   <div data-role="page" id="main" class="cl_main_page">
        <div data-role="content">
                    <div class="responsive_levelbackground"><span>Shop</span></div>
                    <div class="responsive_levelbackground lbl" id="f0"></div>
                    <div class="responsive_levelbackground lbl" id="f1"></div>
                    <div class="responsive_levelbackground lbl" id="f2"></div>
                    <div class="responsive_levelbackground lbl" id="f3"></div>
                    <div class="responsive_levelbackground lbl" id="f4"></div>
                    <div class="responsive_levelbackground">Online Quizz</div>
         </div><!-- /content -->
    </div><!-- /page -->

(I have text being filled into DIvs depending on IDs too..)
Then
textFit(document.getElementsByClassName('responsive_levelbackground'));
does not work
but textFit(document.getElementsByClassName('responsive_levelbackground')[0]);
textFit(document.getElementsByClassName('responsive_levelbackground')[1]);
...
work fine..

Thank you

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.